第一章 Java的基本程序结构

Java 初探,本章先来了解一下 Java 里的基础知识。

一、一个简单的 Java 程序

我们先来看一个最简单的 Java 应用程序,它只发送一条消息到控制台窗口中:

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

这个程序虽然很简单,但所有的 Java 应用程序都具有这种结构,因此还是很值得花一些时间来研究的。首先,Java 区分大小写。如果出现了大小写拼写错误(例如,将 main 拼写成 Main),程序将无法运行。

我们来分析一下这段源代码。首先,关键字 public 称为访问修饰符,这些修饰符用于控制程序的其他部分对这段代码的访问级别。关键字 class 表明 Java 类。类是构建所有 Java应用程序和 applet 的构建块,因此 Java 应用程序中的全部内容都必须放置在类中。

关键字 class 后面紧跟类名。Java 中定义类名的规则很宽松。名字必须以字母开头,后面可以跟字符和数字的任意组合。长度基本上没有限制。但是不能使用 Java 保留字(例如public 或 class)作为类名。标准的命名规范为(FirstSample 就遵循了这个规范):类名是以大写字母开头的名词。如果名字由多个单词组成,每个单词的第一个字母都应该大写(称为骆驼命名法)。

源代码的文件名必须与公共类的名字相同,并用 .java作为扩展名。因此,存储这段源代码的文件名必须是 FirstSample.java。

如果文件名和源代码都没有错误,对文件编译之后,就会得到一个包含这个类字节码的文件 FirstSample.class,并存储在同一个目录下。我们来执行一下这段代码,输入命令:java FirstSample ,控制台上将显示 “Hello world!”。

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

二、注释

Java中的注释不会出现在可执行程序中。因此,我们可以在源代码中根据需要添加任意多的注释,而不必担心可执行代码会膨胀。在Java中,一共有 3 种表示注释的方式。最常用的方式是 //,其注释内容从 // 开始到本行结尾。

// this is a line of text
System.out.println("Hello world!");

当需要更长的注释时,既可以用 //,也可以使用 /* 和 / 将一段比较长的注释括起来。
第 3 种注释可以用来自动生成文档。以 /
* 开始,以 */ 结束。

/**
 * This is the first sample program
 * @author 一支帆
 * @time 2022/3/15 
 */
public class FirstSample {
	public static viod main(String[] args) {
		System.out.println("Hello world!");
	}
}

三、数据类型

Java 中一共有 8 种数据类型,其中有 4 种整型、2 种浮点类型、1 种字符串类型 char和 1 种用于表示真值的 boolean 类型。

1、整型

整形用于表示没有小数部分的数值,允许是负数。Java中提供了 4 中整型:

类型存储需求取值范围
int4 字节-2 147 483 648 ~ 2 147 483 647(刚刚超过 20 亿)
short2 字节-32 768 ~ 32 767
long8 字节-9 223 372 036 854 775 808 ~ 9 223 372 036 854 775 807
byte1 字节-128 ~ 127

在通常情况下,int 类型最常用。但如果想要表示整个地球的居住人口。就需要使用 long 类型了。byte 和 short 类型主要用于特定的应用场合,例如,底层的文件处理或者储存空间很宝贵时的大数组。

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

2、浮点类型

浮点类型用于表示有小数部分的数值。在Java中有两种浮点类型:

类型存储需求取值范围
float4 字节大约 ±3.402 823 47E+38F(有效位数为 6 ~ 7 位)
double8 字节大约 ±1.797 693 134 862 315 70E+308(有效位数为 15 位)

double 表示这种类型的数值精度是 float 类型的两倍(有人称之为双精度数值)。在很多情况下,float 类型的精度(6 ~ 7 位有效数字)并不能满足需求。实际上,只有很少的情况下适合使用 float 类型。
float 类型的数值有一个后缀 F 或 f(例如,3.14F)。没有后缀 F 的浮点数值(如 3.14)总是默认为 double 类型。当然,也可以在浮点数值后面添加后缀 D 或 d(例如 3.14D)。

注意:浮点数值不适用于无法接受舍入误差的金融计算。例如,命令 System.out.pringln(2.0 - 1.1)将打印出 0.89999999999,而不是我们期望的 0.9.这种输入误差的主要原因是浮点数采用二进制系统表示,而在二进制系统中无法精确地表示分数 1/10。这就好像十进制无法精确的表示分数 1/3 一样。如果在数值计算中不允许有任何舍入误差,就应该使用 BigDecimal 类,本章稍后将介绍这个类。

3、char 类型

char 类型的字面量值是要用单括号括起来。例如,‘A’ 是编码值为 65 的字符常量。它与 “A” 不同,“A” 是包含一个字符 A 的字符串。char 类型的值可以表示为十六进制值,其范围从 /u0000 到 /uFFFF。例如,/u2122 表示商标符号(™),/u03C0 表示希腊字母 Π。

4、boolean 类型

boolean(布尔)类型有两个值:false 和 true,用来判断逻辑条件。整型值和布尔值之间不能相互转换。

四、变量与常量

在 Java 中,使用变量来存储值。常量就是值不变的变量。

1、声明变量

每个变量都有一个类型。在生命变量时,先指定变量的类型,然后是变量名。变量名必须是一个以字母开头并由字母或数字构成的序列。不能使用 Java 保留字作为变量名。声明变量示例:

double salary;
int vacationDays;
long earthPopulation;
boolean done;

注意:变量名对大小写敏感,例如,hireday 和 hireDay 是两个不同的变量名。一般来讲,在对两个不同的变量进行命名时,最好不要只存在大小写上的差异。

2、变量初始化

声明一个变量之后,必须用赋值语句对变量进行显示初始化,千万不要使用未初始化的变量的值。例如,Java编译器认为下面的语句序列是错误的:

int vacationDays;
System.out.println(vacationDays); // ERROR--variable not initialized

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

int vacationDays;
vacationDays = 12;

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

int vacationDays = 12;

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

3、常量

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

在 Java 中,经常希望某个常量可以在一个类的多个方法中使用,通常将这些变量称为类常量。可以使用关键字 static final 设置一个类常量。下面是使用类常量的示例:

public class Constant {
	public static final CM_PRE_INCN = 2.54;
	public static void main(String[] args) {
		double paperWidth = 8.5;
		double paperHeight = 11;
		System.out.println("Paper size in centimeters: "
			+ paperWidth * CM_PRE_INCN + " by " + paperHeight * CM_PRE_INCN);
	}
}

4、枚举类型

有时候,变量的取值只在一个有限的集合内。例如,销售的服装或比萨只有小、中、大和超大这四种尺寸。
针对这种情况,可以使用自定义枚举类型。枚举类型包括有限个命名的值。例如,

enum Size { SMALL, MEDIUM, LARGE, EXTRA_LARGE }

就可以声明这种类型的变量:

Size s = Size.MEDIUM;

有关枚举类型的详细内容将在后面介绍。

五、运算符

运算符用于连接值。

1、算术运算符

在 Java 中,使用算数运算符 +、-、*、/ 表示加、减、乘、除运算。当参与 / 运算的两个操作数都是整数时,表示整数除法;否则,表示浮点除法。整数的求余操作(有时称为取模)用 % 表示。例如,15/2 等于 7,15%2 等于 1,15.0/2 等于 7.5。

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

2、数学函数与常量

在 Math 类中,包含了各种各样的数学函数。
要想计算一个数值的平方根,可以使用 sqrt 方法:

double x = 4;
double y = Math.sqrt(x);
System.out.println(y); // prints 2.0

在 Java 中,没有幂运算,因此,要借助 Math 类的 pow 方法:

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

将 y 的值设置为 x 的 a 次幂(x a的次方)。pow 方法有两个 double 类型的参数,其返回类型也是 double 类型。

Math 类还提供了一些常用的三角函数、指数函数、反函数、自然对数等等。

3、数值类型之间的转换

我们经常需要将一种数值类型转换为另一种数值类型。

类型自动转换
上图中有 6 个实线箭头,表示五信息丢失的转换;另外有 3 个虚线箭头,表示可能有精度损失的转换。例如,123 456 789 是一个大整数,它所包含的位数比 float 类型所能够表示的位数多。当将这个整数转换为 float 类型时,将会得到正确的大小,但是会损失一些精度:

int n = 123456789;
float f = n; // f is 1.23456792E8

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

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

4、强制类型转换

在必要的时候,int 类型的值将会自动地转换为 double 类型。但另一方面,有时也需要将 double 转换成 int。在 Java 中,允许进行这种数值之间的类型转换,当然,有可能也会丢失一些信息。这种可能损失信息的转换要通过强制类型转换(cast)来完成。强制类型转换的语法格式是在圆括号中给出想要转换的目标类型,后面紧跟待转换的变量名。例如:

double x = 9.997;
int nx = (int) x;

这样,变量 nx 的值为 9,因为强制类型转换通过截断小数部分将浮点值转换为整型。
如果想对浮点数进行舍入运算,以便得到最接近的整数(在很多情况下,这种操作更有用),那就需要使用 Math.round方法:

double x = 9.997;
int nx = (int) Math.round(x);

5、结合赋值和运算符

可以在赋值中使用二元运算符,这是一种很方便的简写形式。x += 4; 等价于 x = x + 4;

6、自增与自减运算符

以下代码:

int n = 12;
n++;

将 n 的值改为 13.由于这些运算符改变的是变量的值,所以它们不能应用于数值本身。例如,4++ 就不是一个合法的语句。

实际上,这些运算符有两种形式:上面介绍的是运算符放在操作数后面的 “后缀” 形式。还有一种 “前缀” 形式:++n。后缀和前缀形式都会是变量值加 1 或减 1.但在表达式中,二者就有区别了。前缀形式会先完成加 1;而后缀形式会使用变量原来的值。

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

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

7、关系和 boolean 运算符

Java 包含丰富的关系运算符。要检测相等性,可以使用两个符号 ==。另外可以使用 !=。最后还有经常使用的 <、>、<=、>= 运算符。

使用 && 表示逻辑 “与” 运算符,使用 || 表示逻辑 “或” 运算符。从 != 运算符可以想到,感叹号 ! 就是逻辑非运算符。&& 和 || 运算符是按照 “短路” 方式来求值的:如果第一个操作数已经能够确定表达式的值,第二个操作数就不必计算了。

最后一点,Java 支持三元操作符 ? :,这个操作符有时很有用。

8、括号与运算符级别

表达式中,如果不使用圆括号,就按照给出的运算符优先级次序进行计算。同一个级别的运算符按照从左到右的次序进行计算(但右结合运算符除外)。例如,由于 && 的优先级比 || 的优先级高,所以表达式 a && b || c 等价于 (a && b) || c。又因为 += 是右结合运算符,所以表达式 a += b += c 等价于 a += (b += c),也就是将 b += c 的结果(加上 c 之后的 b)加到 a 上。

六、字符串

Java 没有内置的字符串类型,而是在标准 Java 类库中提供了一个预定义类,很自然地叫做 String。每个用双引号括起来的字符串都是 String 类的一个实例。

String e = ""; // an empty string
String greeting = "Hello";

1、字串

String 类的 substring 方法可以从一个较大的字符串中提取出一个字串来。例如:

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

创建一个由字符 “Hel” 组成的字符串。
substring 方法的第二个参数是不想复制的第一个位置。这里要复制位置为 0、1 和 2(从 0 到 2,包括 0 和 2)的字符。在 substring 中从 0 开始计数,直到 3 为止,但不包含 3.

substring 的工作方法有一个优点:容易计算字串的长度。字符串 s.substring(a, b) 的长度为 b - a。例如,字串 “Hel” 的长度为 3 - 0 = 3。

2、拼接

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

String expletive = "Expletive";
String PG13 = "deleted";
String message = expletive + PG13;

上述代码将 “Expletivedeleted” 赋值给变量 message (注意,单词之间没有空格,+ 号完全按照给定的次序将两个字符串拼接起来)。

当将一个字符串与一个非字符串的值进行拼接时,后者会转换成字符串,任何一个 Java 对象都可以转换成字符串。例如:

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

将 rating 的值设置为 “PG13”。

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

String all = String.join(" / ", "S", "M", "L", "XL");
// all is the string "S / M / L / XL"

3、检测字符串是否相等

可以使用 equals 方法检测两个字符串是否相等。对于表达式:s.equals(t)
如果字符串 s 与字符串 t 相等,则返回 true;否则,返回 false。需要注意的是,s 与 t 可以是字符串变量,也可以是字符串字面量。例如,一下表达式是合法的:"Hello.equals(greeting)"

要想检测两个字符串是否相等,而且不区分大小写,可以使用 equalsIgnoreCase 方法。"Hello.equalsIgnoreCase ("hello")"

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

String greeting = "Hello"; // initialize greeting to a string
if (greeting == "Hello") ...
	// probably true
if (greeting.substring(0, 3) == "Hel") ...
	// probably false

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

4、空串与 Null 串

空串 “” 是长度为 0 的字符串。可以调用以下代码检查一个字符串是否为空:if (str.length() == 0)if (str.equals(""))

空串是一个 Java 对象,有自己的串长度(0)和内容(空)。不过,String 变量还可以存放一个特殊的值,名为 null,表示目前没有任何对象与该变量关联。要检查一个字符串是否为 null,要使用以下条件:if (str == null)

有时要检查一个字符串不是 null 也不是空串,这种情况下就需要使用以下条件:if (str != null && str.length() != 0)

5、String API

Java 中的 String 类包含了 50 多个方法。令人惊讶的是它们绝大多数都很有用,可以想见使用的频率非常高。下面的 API 注释汇总了一部分最常用的方法。

  • char charAt(int index)
    返回给定位置的代码单元。除非对底层的代码单元感兴趣,否则不需要调用这个方法。
  • int codePointAt(int index)
    返回从给定位置开始的码点。
  • int offsetByCodePoints(int startIndex, int cpCount)
    返回从 startIndex 码点开始,cpCount 个码点后的码点索引。
  • int compareTo(String other)
    按照字典顺序,如果字符串位于 other 之前,返回一个负数;如果字符串位于 other 之后,返回一个正数;如果两个字符串相等,返回 0.
  • IntStream codePoints()
    将这个字符串的码点作为一个流返回。调用 toArray 将它们放在一个数组中
  • new String(int[] codePoints, int offset, int count)
    用数组中从 offset 开始的 count 个码点构造一个字符串。
  • boolean empty()
  • boolean blank()
    如果字符串为空或者由空格组成,返回 true。
  • boolean equals(Object other)
    如果字符串与 other 相等,返回 true
  • boolean equalsIgnoreCase(String other)
    如果字符串与 other 相等(忽略大小写),返回 true。
  • boolean startsWith(String prefix)
  • boolean endsWith(String suffix)
    如果字符串以 prefix 开头或以 suffix 结尾,则返回 true。
  • int indexOf(String str)
  • int indexOf(String str, int fromIndex)
  • int indexOf(int cp)
  • int indexOf(int cp, int fromIndex)
    返回与字符串 str 或码点 cp 匹配的第一个字串的开始位置。从索引 0 或 fromIndex 开始匹配。如果在原始字符串中不存在 str,则返回 -1。
  • int lastIndexOf(String str)
  • int lastIndexOf(String str, int fromIndex)
  • int lastIndexOf(int cp)
  • int lastIndexOf(int cp, int fromIndex)
    返回与字符串 str 或码点 cp 匹配的最后一个字符串的开始位置。从原始字符串末尾或 fromIndex 开始匹配。
  • int length()
    返回字符串代码单元的个数。
  • int codePointCount(int startIndex, int endIndex)
    返回 startIndex 和 endIndex - 1 之间的码点个数。
  • String replace(CharSequence oldString, CharSequence newString)
    返回一个新字符串。这个字符串用 newString 代替原始字符串中所有的 oldString。可以用 String 或 StringBuilder 对象作为 CharSequence 参数。
  • String substring(int beginIndex)
  • String substring(int beginIndex, int endIndex)
    返回一个新的字符串。这个字符串包含原始字符串中从 beginIndex 到字符串末尾或 endIndex - 1 的所有代码单元。
  • String toLowerCase()
  • String toUpperCase()
    返回一个新字符串。这个字符串将原始字符串中的大写字母改为小写,或者将原始字符串中的所有小写字母改成大写字母。
  • String trim()
  • String strip()
    返回一个新字符串。这个字符串将删除原始字符串头部或尾部小于等于 U+0020 的字符(trim)或空格(strip)。
  • String join(CharSequence delimiter, CharSequence, CharSrquence… elements)
    返回一个新字符串,用给定的定界符连接所有元素。
  • String repeat(int count)
    返回一个字符串,将当前字符串重复 count 次。

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

6、构建字符串

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

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

StringBuilder builder = new StringBuilder();

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

builder.append(ch); // appends a single character
builder.append(str); // appends a string

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

String completedString = builder.toString();

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

下面的 API 注释包含了 StringBuilder 类中的重要方法。

  • StringBuilder()
    构建一个空的字符串构建器。
  • int length()
    返回构建器或缓冲器中的代码单元数量。
  • StringBuilder append(String str)
    追加一个字符串并返回 this。
  • StringBuilder append(char c)
    追加一个代码单元并返回 this。
  • StringBuilder appendCodePoint(int cp)
    追加一个码点,并将其转换为一个或两个代码单元并返回 this。
  • void setCharAt(int i, char c)
    将第 i 个代码单元设置为 c。
  • StringBuilder insert(int offset, String str)
    在 offset 位置插入一个字符串并返回 this。
  • StringBuilder insert(int offset, char c)
    在 offset 位置插入一个代码单元并返回 this。
  • StringBuilder delete(int startIndex, int endIndex)
    删除偏移量从 startIndex 到 endIndex-1 的代码单元并返回 this。
  • String toString()
    返回一个与构建器或缓冲器内容相同的字符串。

七、输入与输出

为了增加后面示例程序的趣味性,需要程序能够接受输入,并适当地格式化输出。当然,现代的程序都是用 GUI 手机用户的输入,然而,编写这种界面的程序需要使用较多的工具与技术,目前还不具备这些条件。我们的第一要务是熟悉 Java 程序设计语言,因此我们要使用基本的控制台来实现输入输出。

1、读取输入

前面已经看到,将输入打印到控制台窗口是一件很容易的事情,只要调用 System.out.println 即可。然而,读取 “标准输入流” System.in 就没有那么简单了。要想通过控制台进行输入,首先需要构造一个与 “标准输入流” System.in 关联的 Scanner 对象。

Scanner in = new Scanner(System.in);

现在,就可以使用 Scanner 类的各种方法读取输入了。例如,nextLine 方法将读取一行输入。

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

在这里,使用 nextLine 方法是因为输入行中有可能包含空格。要想读取一个单词(以空白符作为分隔符),可以调用

String firstName = in.next();

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

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

与此类似,要想读取下一个浮点数,就调用 nextDouble 方法。

java.util.Scanner

  • Scanner(InputStream in)
    用给定的输入流创建一个 Scanner 对象。
  • String nextLine();
    读取输入的下一行内容。
  • String next()
    读取输入的下一个单词(以空格作为分隔符)。
  • int nextInt()
  • double nextDouble()
    读取并转换下一个表示整数或浮点数的字符序列。
  • boolean hasNext()
    检测输入中是否还有其他单词。
  • boolean hasNextInt()
  • boolean hasNextDouble()
    检测是否还有下一个表示整数或浮点数的字符序列。

八、控制流程

1、块作用域

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

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

但是,不能在嵌套的两个块中声明同名的变量。例如,下面的代码就有错误,而无法通过编译:

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

2、条件语句

在 Java 中,一般的条件语句如下所示:

if (yourSales >= target)
{
	performance = "Satisfactory";
	bonus = 100 + 0.01 * (youSales - target);
}
else
{
	performance = "Unsatisfactory";
	bonus = 0;
}

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

反复使用 if…else if… 很常见。例如:

if (yourSales >= 2 * target) 
{
	performance = "Ecellent";
	bonus = 1000;
} 
else if (yourSales >= 1.5 * target) 
{
	performance = "Fine";
	bonus = 500;
} 
else if (yourSales >= target) 
{
	performance = "Satisfactory";
	bonus = 100;
} 
else {
	System.out.println("You're fired");
}

3、循环

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

while (balance < gogal) {
	balance += payment;
	double interest = balance * interestRate / 100;
	balance += interest;
	years++;
}
System.out.println(years + " years.");

while 循环语句在最前面检测循环条件。因此,循环体中的代码有可能一次都不执行。如果希望循环体至少执行一次,需要使用 do/while 循环将检测放在最后。它的语法如下:

do {
	balance += payment;
	double interest = balance * interestRate / 100;
	balance += interest;
	years++;
	// print current balance
	...
	// ask if ready to retire and get input
} while (input.equals("N"));

只要用户回答 “N”,循环就重复执行。

4、确定循环

for 循环语句是支持迭代的一种通用结构,由一个计数器或类似的变量控制迭代次数,每次迭代后这个变量将会更新。下面的循环将数字 1 ~ 10 输出到屏幕上。

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

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

5、多重选择:switch 语句

在处理多个选项时,使用 if/else 结构显得有些笨拙。这时候就用到 switch 语句了。例如:

Scanner in = new Scanner(System.in);
System.out.print("Select an option (1, 2, 3, 4) ");
int choice = in.nextInt();
switch (choice) {
	case 1:
		...
		break;
	case 2:
		...
		break;
	case 3:
		...
		break;
	case 4:
		...
		break;
	default:
		// bad input
		...
		break;
}

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

注意:有可能出发多个 case 分支。如果在 case 分支语句的末尾没有 break 语句,那么,就会接着执行下一个 case 分支语句。这种情况相当危险,常常会引发错误。

case 标签可以是:

  • 类型为 byte、short、int、char的常量表达式。
  • 枚举常量
  • 从 Java 7 开始,case 标签还可以是字符串字面量。
    例如:
String input = ...;
switch (input.toLowerCase()) {
	case "yes": // OK since Java 7
		...
		break;
	...
}

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

Size sz = ...;
switch (sz) {
	case SMALL; // no need to use Size.SMALL
		...
		break;
	...
}

九、大数

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

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

BigInteger a = BigInteger.valueOf(100);

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

BigInteger reallyBig = new BigInteger("22212312321334254342253156345162311");

遗憾的是,不能使用人们熟悉的算术运算符(如:+ 和 *)处理大数,则需要使用大数类中的 add 和 multiply 方法。

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

java.math.BigInteger

  • BigInteger add(BigInteger other)
  • BigInteger subtract(BigInteger other)
  • BigInteger multiply(BigInteger other)
  • BigInteger divide(BigInteger other)
  • BigInteger mod(BigInteger other)
    返回这个大整数和另一个大整数 other 的和、差、积、商以及余数。
  • BigInteger sqrt()
    得到这个 BigInteger 的平方根
  • int cpmpareTo(BigInteger other)
    如果这个大整数与另一个大整数 other 相等,返回 0;如果这个大整数小于另一个大整数 other,返回负数;否则,返回正数。
  • static BigInteger valueOf(long x)
    返回值等于 x 的大整数

java.math.BigDecimal

  • BigDecimal add(BigDecimal other)
  • BigDecimal subtract(BigDecimal other)
  • BigDecimal multiply(BigDecimal other)
  • BigDecimal divide(BigDecimal other)
  • BigDecimal divide(BigDecimal other, RoundingMode mode)
    返回这个大实数与 other 的和、差、积、商。如果商是个无限循环小数,第一个 divide 方法会抛出一个异常。要得到一个舍入的结果,就要使用第二个方法。
  • int compareTo(BigDecimal other)
    如果这个大实数与 other 相等,返回 0;如果这个大实数小于 other,返回负数;否则,返回正数。
  • static BigDecimal valueOf(long x)
  • static BigDecimal valueOf(long x, int scale)
    返回值等于 x 或 x / 10 scale次方 的一个大实数。

十、数组

数组存储相同类型值的序列。

1、声明数组

数组是一种数据结构,用来存储同一类型值的集合。通过一个整型下标(index,或称索引)可以访问数组中的每一个值。例如,如果 a 是一个整型数组,a[i] 就是数组中下标为 i 的整数。

在声明数组变量时,需要指出数组类型(数组元素类型紧跟 [] )和数组变量的名字。下面声明了整型数组 a:int[] a;

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

int[] a = new int[100]// or var a = new int[100]

这条语句声明并初始化了一个可以存储 100 个整数的数组。
数组长度不要求是变量:new int[n] 会创建i一个长度为 n 的数组。

一旦创建了数组,就不能再改变它的长度(不过,当然可以改变单个的数组元素)。如果程序运行中需要经常扩展数组的大小,就应该使用另一种数组结构——数组列表(array list)。

在 Java 中,提供了一种创建数组对象并同时提供初始值的简写形式。下面是一个例子:

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

请注意,这个语法中不需要使用 new,甚至不用指定长度。
最后一个值允许有逗号,如果你要不断为数组增加值,这会很方便:

String[] authers = {
	"James Gosling",
	"Bill Joy",
	"Guy Steele",
	// add more names here and put a comma after each name
}

还可以声明一个匿名数组:

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

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

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

这是下列语句的简写形式:

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

2、访问数组元素

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

String[] names = new String[10];

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

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

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

for (int i = 0; i < a.length; i++)
	System.out.println(i);

3、for each 循环

Java 有一种功能很强的循环结构,可以i用来一次处理数组(或者其他元素集合)中的每个元素,而不必考虑指定下标值。

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

for (variable: collection) statement

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

for (int element: a)
	System.out.println(element);

打印数组 a 的每一个元素,一个元素占一行。

当然,使用传统的 for 循环也可以获得同样的效果:

for (int i = 0; i < a.length; i++)
	System.out.println(a[i]);

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

有一个更加简单的方式可以打印数组中大哥所有值,即利用 Arrays 类的 toString 方法。调用 Arrays.toString(a),返回一个包含数组元素的字符串,这些元素包围在中括号内,并用逗号分隔,例如,”[2, 3, 5, 7, 11,13]”。要想打印数组,只需要调用
System.out.println(Arrays.toString(a))

4、数组拷贝

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

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

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

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

如果数组是数值型,那么额外的元素将被赋值为 0;如果数组元素时布尔型,则将赋值为 false。相反,如果长度小于原始数组的长度,则只拷贝前面的值。

5、数组排序

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

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

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

java.util.Arrays

  • static String toString(xxx[] a)
    返回包含 a 中元素的一个字符串,这些元素用中括号包围,并用逗号分隔。在这个方法以及后面的方法中,数组元素类型 xxx 可以是 int、long、char、byte、boolen、float 或 double。
  • static xxx[] copyOf(xxx[] a, int end)
  • static xxx[] copyOfRange(xxx[] a, int start, int end)
    返回与 a 类型相同的一个数组,其长度为 length 或者 end-start,数组元素为 a 的值。如果 end 大于 a.length,结果会填充 0 或 false 值。
  • static void sort(xxx[] a)
    使用优化的快速排序算法对数组进行排序。
  • static int binarySearch(xxx[] a, xxx v)
  • static int binarySearch(xxx[] a, int start, int end, xxx v)
    使用二分查找算法在有序数组 a 中查找值 v。如果找到 v,则返回相应的下标;否则,返回一个负数值 r。-r-1 是 v 应插入的位置(为保持 a 有序)。
  • static void fill(xxx[] a, xxx v)
    将数组的所有数据元素设置为 v。
  • static boolean equals(xxx[] a, xxx[] b)
    如果两个数组大小相同,并且下标相同的元素都对应相等,返回 true。

现在,我们已经了解了 Java 语言的基本程序结构。下一章将介绍 Java 中最核心的知识点:面向对象。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一支帆

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值