第三章 Java的基本程序设计结构
3.1 一个简单的Java应用程序
Main.java
public class Main {
public static void main(String[] args) {
System.out.println("hello world!");
}
}
- public称为访问修饰符,用于控制程序的其他部分对这段代码的访问级别
- 关键字class表明Java程序中的全部内容都包含在类中。这里,只需要将类作为一个加载程序逻辑的容器,程序逻辑定义了应用程序的行为。Java应用程序中的全部内容都必须放置在类中。
- 关键字class后面紧跟类名。类名是以大写字母开头的名词。如果名字由多个单词组成,每个单词的第一个字母都应该大写
- 源代码的文件名必须与公共类的名字相同,并用.java作为扩展名.
- Java编译器将字节码文件自动地命名为Main. class,并与源文件存储在同一个目录下。最后,使用下面这行命令运行这个程序:(请记住,不要添加.class扩展名。)
java Main
- 因此为了代码能够执行,在类的源文件中必须包含一个main方法。main方法必须声明为public。
- Java中的所有函数都属于某个类的方法。因此,Java中的main方法必须有一个外壳类。Java中的main方法必须是静态的。
- 使用了System.out对象并调用了它的println方法。注意,点号(·)用于调用方法。Java使用的通用语法是
object.method(parameters);
3.2 注释
最常用的方式是使用//,其注释内容从//开始到本行结尾。
当需要长篇的注释时,既可以在每行的注释前面标记//,也可以使用/和/将一段比较长的注释括起来。
最后,第3种注释可以用来自动地生成文档。这种注释以/*开始,以/结束。
3.3 数据类型
Java是一种强类型语言。这就意味着必须为每一个变量声明一种类型。
在Java中,一共有8种基本类型(primitive type),其中有4种整型、2种浮点类型、1种用于表示Unicode编码的字符单元的字符类型char(请参见论述char类型的章节)和1种用于表示真值的boolean类型。
3.3.1 整型
允许是负数。
在Java中,整型的范围与运行Java代码的机器无关。
由于Java程序必须保证在所有机器上得到相同的运行结果,所以数据类型的取值范围必须固定。
3.3.2 浮点类型
浮点类型表示有小数部分的类型。
double类型的数值精读是float的两倍。
float类型的数值后缀有一个f标识。
3.3.3 char类型
char类型可用于表示单个字符和unicode字符。
char类型的字面值要用引号引起来。
3.3.5 boolean类型
boolean类型有两个值:true和false。整型值和boolean值之间不能进行转换。
3.4 变量
在Java中,每个变量都有一个类型(type)。在声明变量时,变量的类型位于变量名之前。这里列举一些声明变量的示例:
double salary;
int vacationDays;
boolean done;
变量名必须是一个以字母开头并由字母或数字构成的序列。
变量名中所有的字符都是有意义的,并且大小写敏感。变量名的长度基本上没有限制。
3.4.1 变量初始化
声明一个变量之后,必须用赋值语句对变量进行显式初始化,千万不要使用未初始化的变量。
例如,Java编译器认为下面的语句序列是错误的:
int vacationDays;
System.out.println(vacationDays);
要想对一个已经声明过的变量进行赋值,就需要将变量名放在等号(=)左侧,相应取值的Java表达式放在等号的右侧。
也可以将变量的声明和初始化放在同一行中。
int vacationDays;
vacationDays = 12;
int days = 31;
在Java中,变量的声明尽可能地靠近变量第一次使用的地方,这是一种良好的程序编写风格。
在Java中,不区分变量的声明与定义。
3.4.2 常量
在Java中,利用关键字final指示常量。例如:
final double CM_PER_INCH = 2.54;
关键字final表示这个变量只能被赋值一次。一旦被赋值之后,就不能够再更改了。习惯上,常量名使用全大写。
在Java中,经常希望某个常量可以在一个类中的多个方法中使用,通常将这些常量称为类常量。可以使用关键字static final设置一个类常量。
static final double CM_PER_INCH = 2.54;
3.5 运算符
在Java中,使用算术运算符+、-、*、/表示加、减、乘、除运算。当参与/运算的两个操作数都是整数时,表示整数除法;否则,表示浮点除法。整数的求余操作(有时称为取模)用%表示。
3.5.1 数学函数与常量
在Math类中,包含了各种各样的数学函数。在编写不同类别的程序时,可能需要的函数也不同。
// 静态导入头文件
import static java.lang.Math.*;
// 平方根
double x = 4;
double y = Math.sqrt(x);
// 幂运算
int a = 2;
double z = Math.pow(x, a);
//
static final double CM_PER_INCH = 2.54;
3.5.2 数值类型之间的转换
经常需要将一种数值类型转换为另一种数值类型。
下图表示合理的转换。
6个实心箭头,表示无信息丢失的转换;有3个虚箭头,表示可能有精度损失的转换。
3.5.3 强制类型转换
在必要的时候,int类型的值将会自动地转换为double类型。但另一方面,有时也需要将double转换成int。在Java中,允许进行这种数值之间的类型转换。当然,有可能会丢失一些信息。在这种情况下,需要通过强制类型转换(cast)实现这个操作。强制类型转换的语法格式是在圆括号中给出想要转换的目标类型,后面紧跟待转换的变量名。
double x = 9.997;
int nx = (int)x;
如果想对浮点数进行舍入运算,以便得到最接近的整数(在很多情况下,这种操作更有用),那就需要使用Math.round方法:
double x = 9.997;
int nx = (int)Math.round(x);
3.5.7 位运算符 &、|、~、^、>>、<<
位运算符包含与运算符、或运算符、取反运算符、异或运算符、左移运算符和右移运算符。
& 与运算符
规则 与运算时,进行运算的两个数,从最低位到最高位,一一对应。如果某 bit 的两个数值对应的值都是 1,则结果值相应的 bit 就是 1,否则为 0.
| 运算符
与运算时,进行运算的两个数,从最低位到最高位,一一对应。如果某 bit 的两个数值对应的值只要 1 个为 1,则结果值相应的 bit 就是 1,否则为 0。
~ 取反运算符
对操作数的每一位进行操作,1 变成 0,0 变成 1。
^ 异或运算符
两个操作数进行异或时,对于同一位上,如果数值相同则为 0,数值不同则为 1。
值得注意的是 3 ^ 5 = 6,而 6 ^ 5 = 3。
针对这个特性,我们可以将异或运算作为一个简单的数据加密的形式。比如,将一个mp4文件所有数值与一个种子数值进行异或得到加密后的数据,解密的时候再将数据与种子数值进行异或一次就可以了。
所以说异或运算可以作为简单的加解密运算算法。
>> 右移运算符
a >> b = a / ( 2 ^ b ) ,所以 5 >> 1= 5 / 2 = 2,11 >> 2 = 11 / 4 = 2。
<< 左移运算符
如果某个数值右移 n 位,就相当于拿这个数值去除以 2 的 n 次幂。如果某个数值左移 n 位,就相当于这个数值乘以 2 ^ n。
3.5.8 枚举类型
enum Size {
SMALL,MEDIUM,LARGE};
Size s = Size.MEDIUM;
Size类型的变量只能存储这个类型声明中给定的某个枚举值,或者null值,null表示这个变量没有设置任何值。
3.6 字符串
在标准Java类库中提供了一个预定义类,很自然地叫做String。每个用双引号括起来的字符串都是String类的一个实例:
String str = "hello";
// 子串
String substr = str.substring(0,3);
// 拼接
String hello = "hello ";
String world = "world.";
String helloWorld = hello + world;
// 字符串比较
int res = hello.equals(world);
res = hello.equalsIgnoreCase(world);
// 判断空字符串
if (str != NULL && str.length() == 0) {
}
// 获取字符
char c = str.charAt(0);
// 以字符串开头
boolean start = str.startsWith("hu");
// 子串的位置
int index = str.indexOf("hhhh");
// 长度
int length = str.length();
由于不能修改Java字符串中的字符,所以在Java文档中将String类对象称为不可变字符串.
空串是一个Java对象,有自己的串长度(0)和内容(空)。不过,String变量还可以存放一个特殊的值,名为null,这表示目前没有任何对象与该变量关联。
构建字符串
有些时候,需要由较短的字符串构建字符串,例如,按键或来自文件中的单词。采用字符串连接的方式达到此目的效率比较低。每次连接字符串,都会构建一个新的String对象,既耗时,又浪费空间。使用StringBuilder类就可以避免这个问题的发生。
StringBuilder builder = new StringBuilder();
builder.append("hello ");
builder.append("world ");
builder.append("2021 ");
System.out.println(builder.toString());
在JDK5.0中引入StringBuilder类。这个类的前身是StringBuffer,其效率稍有些低,但允许采用多线程的方式执行添加或删除字符的操作。如果所有字符串在一个单线程中编辑(通常都是这样),则应该用StringBuilder替代它。这两个类的API是相同的。
3.7 输入输出
要想通过控制台进行输入,首先需要构造一个Scanner对象,并与“标准输入流”System.in关联。
nextLine方法将输入一行。
要想读取一个整数,就调用nextInt方法。
Scanner类定义在java.util包中。
Scanner in = new Scanner(System.in);
String name = in.nextLine();
int age = in.nextInt();
文件输入输出
要想对文件进行读取,就需要一个用File对象构造一个Scanner对象。
要想写入文件,就需要构造一个PrintWriter对象。在构造器中,只需要提供文件名:
Scanner in = new Scanner(Path.get("demo.txt"),"UTF-8");
PrintWriter out = new PrintWriter("demo.txt)","UTF-8");
String name = in.nextLine();
int age = in.nextInt();
3.8 控制流程
块作用域
块(即复合语句)是指由一对大括号括起来的若干条简单的Java语句。块确定了变量的作用域。一个块可以嵌套在另一个块中。
public static void main(String[] args) {
int n;
// 块语句
{
int k;
}
}
中断
Java还提供了一种带标签的break语句,用于跳出多重嵌套的循环语句。有时候,在嵌套很深的循环语句中会发生一些不可预料的事情。此时可能更加希望跳到嵌套的所有循环语句之外。通过添加一些额外的条件判断实现各层循环的检测很不方便。这里有一个示例说明了break语句的工作状态。请注意,标签必须放在希望跳出的最外层循环之前,并且必须紧跟一个冒号。
int i = 0;
read_data:
while (true)
{
for (; i < 10; i++) {
if (i > 8) {
break read_data;
}
}
}
3.9 大数值
如果基本的整数和浮点数精度不能够满足需求,那么可以使用java.math包中的两个很有用的类:BigInteger和BigDecimal。这两个类可以处理包含任意长度数字序列的数值。BigInteger类实现了任意精度的整数运算,BigDecimal实现了任意精度的浮点数运算。
使用静态的valueOf方法可以将普通的数值转换为大数值
遗憾的是,不能使用人们熟悉的算术运算符(如:+和*)处理大数值。而需要使用大数值类中的add和multiply方法。
BigInteger a = BigInteger.valueOf(1000000000);
BigInteger b = BigInteger.valueOf(2000000000);
BigInteger c = a.add(b);
BigInteger d = a.multiply(b);
3.10 数组
数组是一种数据结构,用来存储同一类型值的集合。通过一个整型下标可以访问数组中的每一个值。
例如,如果a是一个整型数组,a[i]就是数组中下标为i的整数。在声明数组变量时,需要指出数组类型(数据元素类型紧跟[])和数组变量的名字。
下面声明了整型数组a
int[] a;
不过,这条语句只声明了变量a,并没有将a初始化为一个真正的数组。应该使用new运算符创建数组。
int[] a = new int[10];
这条语句创建了一个可以存储100个整数的数组。数组长度不要求是常量:newint[n]会创建一个长度为n的数组。
创建一个数字数组时,所有元素都初始化为0。boolean数组的元素会初始化为false。对象数组的元素则初始化为一个特殊值null,这表示这些元素(还)未存放任何对象。
要想获得数组中的元素个数,可以使用array.length
int[] arr = new int[10];
for (int i = 0; i < 10; i++) {
arr[i] = i;
}
// for each循环
for (int a : arr) {
System.out.println(a);
}
// 初始化
int[] smallPrimes = {
2,3,5,7,11,13};
int[] arr2 = arr;
// 拷贝数组
int[] copyArr = Arrays.copyOf(arr, arr.length);
// 数组排序
Arrays.sort(arr);
// 二维数组
double[][] balances = new double[10][12];
int[][] magic = {
{
1,2,3,4},
{
5,6,7,8},
{
9,10,11,12},
{
13,14,15,16}
};
在Java中,允许将一个数组变量拷贝给另一个数组变量。这时,两个变量将引用同一个数组。
如果希望将一个数组的所有值拷贝到一个新的数组中去,就要使用Arrays类的copyOf方法。