ch3 Java 的基本程序设计结构
ch3 Java 的基本程序设计结构
数据类型
-
Java 是一种强类型语言。
-
Java 中,一共有 8 中基本类型
- 其中有 4 种整型、2 种浮点类型、1 中字符类型 char 和 1 中表示真值的 boolean 类型。
Java 中有一个能有表示任意精度的算书包,称之为“大数”。但是它不是一种基本 Java 类型,而是一个 Java 对象。
整型 【4 种】
- 带有后缀 L或者 l 表示 long; (eg:40L)
- 前缀 0x 或 0X 表示十六进制数值; (eg:0xCAFE)
- 前缀 0 表示八进制;(eg:010表示十进制8。容易混淆,最好别用)
- 前缀 0b 或 0B 表示二进制数。 (eg:0b1001,就是9)
浮点类型 【2 种】
- 带有后缀 F 或 f 表示 float 类型;
- 不带后缀的浮点类型表示 double,也可以带后缀 D 或者 d
char 类型
转义序列
变量与常量
-
千万不能使用没有初始化的变量
-
变量的声明尽可能靠近变量第一次使用的地方(非强制,但是可读性更高)
-
用 final 修饰的表示常量
- 关键字 final 表示这个变量只能被赋值一次。一旦被赋值后,就不能在更改了 。
- 习惯上,常量名使用全大写
-
枚举类型:变量取值只在一个有限的集合内。
enum Size {SMALL, MEDIUM, LARGE, EXTRA_LARGE}
运算符
数值类型之间的转换
- 6 个实现箭头,表示无信息丢失的转换;
- 3 个虚线箭头,表示可能有精度损失的转换
当用一个二元运算符两个值时,先要将两个操作数转化为同一种类型,然后再进行计算:
- 如果两个操作数中有一个是 double 类型,另一个操作数就会转换为 double 类型;
- 否则,如果其中一个操作数是 float 类型,另一个操作数将会转换为 float 类型;
- 否则,如果其中一个操作数是 long 类型,另一个操作数将会转换为 long 类型;
- 否则, 两个操作数都将被转换为 int 类型。
强制类型转化
-
可能造成信息丢失
-
语法格式:在圆括号中给出想要转换的目标类型,后面紧跟待转换的变量名
double x = 8.98; int nx = (int) x; // 强制类型转换 double -> int
结合赋值和运算符
-
可以在赋值中使用二元运算符
x += 4; 等价与 x = x + 4;
-
注意: 如果运算符得到一个值,其类型与左侧操作数的类型不同,就会发生强制类型转换。
例如,如果x是一个int,则以下语句
x += 3.5;
是合法的,将把 x 设置为(int)(x + 3.5)。
自增与自减运算符
- 后缀形式:i++、i–
- 前缀形式:++i、–i
- 前缀形式先完成加一;而后缀形式会使用变量原来的值
最好不要在表示式中使用 ++,这样容易迷惑…
关系运算符
-
关系运算符:>、<、>=、<=、==、!=
-
逻辑运算符:&&(逻辑 与)、||(逻辑 或)、!(逻辑 非)
-
&& 和 || 运算符是按照“短路”方式来求值的
- 如果第一个操作数已经能够确定表达式的值,第二个操作数就不必计算了。
- 因此,可以利用这一点来避免错误。
例如,x != 0 && 1 / x > x + y 可以避免 0 作为分母
位运算符
- & (“and”)、 | (“or”)、 ^ (“XOR”)、 ~ (“not”)
- & 和丨运算符不采用“ 短路” 方式来求值
- 左移 << 、右移 >>
- n << i,将 n 的二进制左移 i 位,相当于将 n 扩大 2^i 倍;
- n << i = n * 2^i;
- eg:3 << 2 等于 12
- n >> i,将 n 的二进制右移 i 位,相当于将 n 缩小 2^i 倍;
- n >> i = n / (2^i);
- eg:12 >> 1等于 6
- n << i,将 n 的二进制左移 i 位,相当于将 n 扩大 2^i 倍;
- 无符号右移 >>>
- “>>>” 只用 0 填充最高位,而 “>>”是用符号位填充高位
- 注意:不存在 “<<<" 运算符。
括号与运算符优先级
- && 的优先级比|| 的优先级高
字符串
子串
- s.substring(i, j) ; // 左闭右开区间,取 s 中索引介于 [i, j) 的子串
拼接
-
“+” 拼接两个字符串
- 当将一个字符串与一个非字符串的值进行拼接时,后者被转换成字符串
-
如果需要把多个字符串放在一起, 用一个定界符分隔,可以使用静态 join 方法
- String all = String.join(" / ", “S”, “M”, “L”, “XL”); // all is the string "S / H / L / XL
-
repeat 方法 (java11提供)
- String s = “a”.repat(3); // s is “aaa”
不可变字符串
-
String 类没有提供用于修改字符串的方法,不能直接修改字符串中的字符
- 以下代码中如想,修改greeting 的内容修改为“ Help!”,不能直接地将greeting的最后两个位置的字符修改为 ‘p’ 和 ‘!’。做法应为,子串拼接
String greeting = “Hello”;
greeting = greeting.substring(0, 3) + “p!”; // 子串拼接
-
不能修改其中的任何一个字符,但是可以修改字符串变量 greeting, 让它引用另外一个字符串
-
拼接字符串比直接修改一个代码单元效率低,那为什么还用?
-
不可变字符串却有一个优点
- 编译器可以让字符串共享。
为了弄清具体的工作方式, 可以想象将各种字符串存放在公共的存储池中。字符串变量指向存储池中相应的位置。如果复制一个字符串变量, 原始字符串与复制的字符串共享相同的字符。
-
总而言之,Java 的设计者认为共享带来的高效率远远胜过于提取、拼接字符串所带来的低效率。
检测字符串是否相等
- 用 equals 方法检测两个字符串内容是否相等
- 不要使用 == 运算符检测两个字符串是否相等,== 只能检测两个字符串使用同一个引用
空串与 Null 串
-
空串"" 是长度为0 的字符串
- 空串是一个Java 对象, 有自己的串长度( 0 ) 和内容(空)
-
null 串, 这表示目前没有任何对象与该变量关联
-
检查一个字符串既不是null 也不为空串
- if (str != null && str.length() != 0)
- 两条语句不可颠倒,这里使用逻辑“短路”,避免了 null 调用 lenght() 方法
码点与代码单元
没细看这个码点是个啥…
构建字符串
-
由较短的字符串构建字符串,采用字符串连接的方式达到此目的效率比较低
-
每次连接字符串, 都会构建一个新的String 对象,既耗时, 又浪费空间
-
使用 StringBuilder 类就可以避免这个问题的发生
即,如果需要频繁拼接字符串时,改用 StringBuilder 代替 String。
StringBuilder builder = new StringBuilder();
builder.append(ch); // appends a single character
bui1der.append(str); // appends a string
String completedString = builder.toString(); // 转换为 String
输入输出
读取输入
-
首先需要构造一个Scanner 对象,并与“ 标准输人流” System.in 关联
- Scanner in = new Scanner(System.in);
-
Scanner 类中的方法
- nextLine()方法:输入一行(允许输入行中包括空格)
- next() 方法:读取一个单词(以空白符作为分隔符),
- nextlnt() 方法:读取一个整数
- nextDouble() 方法:读取下一个浮点数
-
因为输入是可见的, 所以Scanner 类不适用于从控制台读取密码
- 使用 Console 类 实现密码读取 (书上p56)
格式化输出
- 使用 System.out.printf 方法实现格式化输出;
- 可以使用静态的String.format 方法创建一个格式化的字符串, 而不打印输出
- 格式化输出日期:t 开始 (书上p59)
- 参数索引:索引必须紧跟 % 后面,以 $ 终止,索引从 1 开始,而不是从 0 开始
文件输入与输出
Path.of() 函数是 Java11特性,但是本机装的是 jdk1.8,没用成。
尝试了下装 java11,结果炸了。又回退到 jdk1.8 了
暂时跳过这里…
控制流程
块作用域
- 块(即复合语句)是指由一对大括号括 {} 起来的若干条简单的 Java 语句
- 一个块可以嵌套在另一个块中,但不能在嵌套的两个块中声明同名的变量
条件语句
- if (condition) statement
- if (condition) statementi else statementi
- else 子句与最邻近的 if 构成一组
循环
-
while {condition ) statement
- while 循环语句首先检测循环条件
- 循环体中的代码有可能一次都不被执行
-
do statement while { condition);
- do while 先执行语句(通常是一个语句块),再检测循环条件;
- do while 至少执行一次循环体代码
-
for (int i = 1; i <= 10; i++)
- for 语句的第1 部分通常用于对计数器初始化; 第2 部分给出每次新一轮循环执行前要检测的循环条件; 第3 部分指示如何更新计数器。
- for 语句的3 个部分应该对同一个计数器变量进行初始化、检测和更新。(不是硬性规定,但是如果不遵守这个可能造成代码晦涩难懂)
-
泛型循环 : foreach 循环
在循环中,检测两个浮点数是否相等需要格外小心
for (double x = 0; x != 10; x += 0 . 1) . . .
可能永远不会结束。由于舍入的误差, 最终可能得不到精确值。
在上面的循环中,因为 0.1 无法精确地用二进制表示, 所以,x 将从9.999 999 999 999 98 跳到
10.099 999 999 999 98
多重选择:switch 语句
- 有可能触发多个case 分支;
- 如果在case 分支语句的末尾没有break 语句, 那么就会接着执行下一个case 分支语句
所以,使用 switch 时每个单独情况的 case 后面一定要带上 break;多个 case 处理同一情况,则不必加 break
- case 标签可以是:
- 类型为 char、byte、short 或 int 的常量表达式
- 枚举常量
- 从 Java 7 开始, case 标签还可以是字符串字面量
中断控制流程语句
-
goto 是 Java 的保留字,但是在 Java 中并未使用它;
-
break : 跳出本层循环;
-
带标签的break 语句 : 用于跳出多重嵌套的循环语句
-
标签必须放在希望跳出的最外层循环之前, 并且必须紧跟一个冒号。
read_data: // 标签 while (. . .) { // this loop statement is tagged with the label for (. . .) { // this inner loop is not labeled ... if (n < 0) { // should never happen-can’t go on break read_data; // break out of readjata loop } } }
-
事实上,可以将标签应用到任何语句中, 甚至可以应用到 if 语句或者块语句中
-
注意, 只能跳出语句块,而不能跳入语句块。
-
-
continue 语句 : 越过了当前循环体的剩余部分, 立刻跳到循环首部
- while 和 do while 循环中 continue 要慎用,使用不当可能会造成死循环
- 在 while 和 do while 中慎用 continue
大数
Java 基本类型中整数和浮点数精度不可能能够满足需求,java.math 中的 Biglnteger 和 BigDecimal 这两个类可以处理任意长度序列的数值。
-
Biglnteger 类实现了任意精度的整数运算;
-
BigDecimal 实现了任意精度的浮点数运算。
-
使用静态的 valueOf 方法可以将普通的数值转换为大数值
- Biglnteger a = Biglnteger.valueOf(100) ;
-
不能使用人们熟悉的算术运算符(如:+ 和*) 处理大数值,而是使用大数值类中的add 和multiply 方法
- Biglnteger c = a.add(b) ; // c = a + b
- Biglnteger d = c.nul ti pi y(b.add(Biglnteger.val ueOf(2))); // d = c * (b + 2)
-
大数不是 Java 基本数据类型,而是对象。
与 C++ 不同,Java 没有提供运算符重载功能,程序员无法重定义+ 和 * 运算符
数组
声明和创建数组
-
数组用来存储同一类型值的集合
-
声明数组变量 : int[] a;
- int[] a;
- 以上这条语句只声明了变量a, 并没有将a 初始化为一个真正的数组
Java 中 有两种声明数组的方式 : int[] a; 或 int a[];
大多数 Java 应用程序员喜欢使用第一种风格,它将类型int[] ( 整型数组)与变量名分开了,更加清晰直观
-
创建数组 : 使用 new 运算符创建数组
- new int[n] 会创建一个长度为 n 的数组
-
数组创建初始化
- 使用 new 关键字创建一个数字数组时, 所有元素都初始化为 0;
- boolean 数组的元素会初始化为 false;
- 对象数组的元素则初始化为一个特殊值 null , 这表示这些元素(还)未存放任何对象。
-
一旦创建了数组, 就不能再改变它的大小(但是可以改变数组中每个元素数值,这点不同于 Java 中的 String)
数组初始化和匿名数组
-
创建数组的简写形式: int[] arr = {1, 3, 4};
- 在使用这种语句时,不需要调用new
-
创建匿名数组
-
new int[] {1, 2, 3};
-
这会创建一个新数组并利用大括号中提供的值进行初始化。它会统计初始值个数,并相应地设置数组大小
arr = new int {1, 2, 4}; // 本条语句是下面两条语句的简写形式 // 这里不能直接arr = {1, 2, 4}; 只有第一次创建数组时候能这样写,比如下面创建 temp 数组时 // 等价于 int[] temp = {1, 2, 4} arr = temp;
-
-
长度为 0 的数组
- 作用 : 在编写一个结果为数组的方法时,如果碰巧结果为空,此时可以创建一个长度为 0 的数组
- 创建方式: new elementType[0] 或 new elementType[] {}
注意:数组长度为 0 与 null 不同
数组拷贝
数组是引用类型变量,栈区存放对象的引用,堆区存放真正的数组中的数据
-
数组浅拷贝:将一个数组变量拷贝给另一个数组变量
-
这时, 两个变量将引用同一个数组*(只是拷贝了引用,一变则全变)*
int[] luckyNumbers = smallPrimes; 1uckyNumbers[S] = 12; // now smallPrimes[5] is also 12
-
-
数组深拷贝:使用 Arrays 类的 copyOf 方法
-
将一个数组的所有值拷贝到一个新的数组中去*(两个数组没什么关系,一个变另外一个不变)*
int[] copiedLuckyNumbers = Arrays.copyOf(luckyNumbers, luckyNumbers.length) ; // 第2 个参数是新数组的长度,如果长度大于当前数组长度,则剩余部分被创建初始化为默认值(int:0,boolean:false,对象:null);如果长度小于当前数组长度,只拷贝最前面的数据元素
-
命令行参数
- main 方法中的 String arg[],即为命令行参数
- 如果使用下面这种形式运行这个程序
-
java Message -g cruel world
-
args 数组内容
- args[0] : “-g”
- args[1] : “cruel”
- args[2] : “world”
-
Java 的 main 方法中,程序名并没有存储在 args 数组中
- args[0] 是“ -h”, 而不是“ Message” 或“ java”
-
数组排序
- 使用 Arrays 类中的 sort 方法
- 这个方法使用了优化的快速排序算法
- 时间复杂度:平均 O(n lgn)
多维数组
-
二维数组
- int[][] arr = {{1, 2}, {3, 4}};
-
Java 实际上没有多维数组, 只有一维数组。
-
多维数组被解释为“ 数组的数组”
-
“不规则” 数组 : 即数组的每一行有不同的长度
-
创建一个矩阵是三角形的,第 i 行有 i + 1 个元素
final int NMAX = 10; // allocate triangular array int[] [] odds = new int[NMAX + 1][]: for (int n = 0; n <= NMAX; n++) odds[n] = new int[n + 1];
-
快速打印数组元素
-
打印一维数组
- 利用 Arrays 类中的 toString 方法
- 调用 Arrays.toString(arr),返回一个包含数组元素的字符串
int[] arr = {1, 2, 3}; System.out.println(Arrays.toString(arr)); // 输出:[1, 2, 3]
-
打印二维数组
- 利用 Arrays 类中的 deepToString 方法
int[][] arr = {{1, 2}, {3, 4}}; System.out.println(Arrays.deepToString(arr)); // 输出:[[1, 2], [3, 4]]
利用 for 和 foreach 也可达到效果,但是直接调用函数更快 (调包侠上线,哈哈哈…)