《JAVA核心技术 卷I》第三章 - Java的基本程序设计结构
第三章-Java的基本程序设计结构
[toc]
1. 数据类型
1.1 char类型
char类型的字面量值需要用单引号括起来。如
‘A’
是编码值为65的字符常量,而"A"
为包含一个字符A的字符串char类型的值可以表示为16进制值(\u0000~\uFFFF)。例如,
'\u2122'
表示™
转义序列:
- 类似
\u
的字符被称为"转义序列",除了\u外还有许多转义序列(在C语言中它们称为转义字符,如\n)
转义序列 名称 \b 退格 \t 制表 \n 换行 \r 回车 \ " 双引号 \ ' 单引号 \ \ 反斜杠 \u
比较特殊,它可以出现在加引号的字符常量和字符串之外,这可能会导致一些隐性的错误- 如:
//\u000A is a newline
这行不会被当作注释,应为\u00A0会被替换为一个换行符 - 同理:
// look inside c:\users
会产生一个语法错误,因为\u后面没有接有效值 - 转义序列会在解析代码之前处理,也就是说
"\u0022+\u0022"
实际上会被执行为""+""
(空串连接)而不是"+"
(包含+号的字符串)
- 如:
- 类似
1.2 Unicode和char类型
**码点:**与一个编码表中的某个字符对应的代码值;码点采用16进制书写(U+0014 = 'A')
**代码平面:**Unicode的码点可以分成17个代码平面;第一个代码平面被称为基本多语言平面(U+0000~U+FFFF);其余的16个代码平面瓜分U+10000到U+10FFFF(U+FFFFF + U+FFFF)
"UTF-16编码"采用不同长度的编码表示所有Unicode码点;在基本多语言平面中,每个字符用16bits表示,这些字符通常称为代码单元;而辅助字符(存在于其余16个代码平面中的字符)被编码为"一对连续的代码单元",采用这种编码对表示的各个值会落入基本多语言平面中未用的2048个值范围内(基本多语言面并没有用完,多出来的这一部分被划了一部分用于表示辅助字符的编码对,这片区域被称为替代区域)
- 举例:八元数集O,码点为U+1D546,不位于基本多语言平面,属于辅助字符;在UTF-16下,被编码为U+D835和U+DD46,这个编码对接下来会落入基本多语言平面中
在Java中,char类型描述了UTF-16编码中的一个代码单元(也就是说,单个char类型变量,是无法表示辅助字符的)
强烈建议不要在程序中使用char类型,除非确实需要处理UTF-16代码单元,最好将字符串作为抽象数据类型处理
1.3 boolean类型
- Java中不能通过以下代码if(x = 0),x=0不会被转化为布尔值,也就无法通过判断
2.变量与常量
2.1 变量
变量名必须是由字母开头的,以字母或数字构成的序列。Java中对于字母和数字的定义要比常见的大:字母包括"A-Z","a-z","_","$"或在某种语言中表示字母的任何Unicode字符(这意味着中文名变量理论上是可行的);数字包括"0~9"和在某种语言中表示数字的任何Unicode字符(但不包括辅助字符)
- 不推荐在变量名中包含"$",它只用在Java编译器或者其它工具生成的名字中
对于局部变量,如果可以从变量的初始值推断出它的类型,就不需要声明类型。只需要使用关键字var而无需指定类型
var vacation = 12; //var自动识别为int var greeting = "Hello"; //var自动识别为String
2.2 常量
- 关键字final表示这个变量只能被赋值一次;可以使用关键字static final设置一个类常量,类常量可以在一个类的多个方法中使用,类常量的定义位于main方法的外部,如果要让其他类也能访问这个常量,可以加一个public
2.3 枚举类型
**定义:**有时候,变量的取值只在一个有限的集合内,针对这种情况,可以自定义枚举类型。枚举类型包括有限个命名的值。
enum Size {SMALL,MEDIUM,LARGE,EXTRA_LARGE}; Size s = Size.MEDIUM; //输出s显示MEDIUM
Size类型的变量只能储存这个类型声明中给定的某个枚举值,或者特殊值null,null表示这个变量没有设置任何值
3.运算符
floorMod(Math类的一种方法),可以让负数取模时仍返回一个正的余数(符合一般数学定律);但如果取模的数是一个负数,则仍会返回一个负的余数
如果希望得到一个更加准确的结果而不在意性能的话,可以使用StrictMath类;Math类还提供了一些方法使整数由更好的运算安全性
- 例:常规运算下1000000000 * 3的计算结果将是一个负数(int类型溢出);但如果使用Math.multiplyExact(1000000000 * 3),就会生成一个异常。此外对于其它数据类型和运算类型还有对应的方法(addExact,substrateExact等)
当用一个二元运算符连接两个值时,先要将两个操作数转换为同一种类型,然后再进行计算
- 转化的优先级为double > float > long > int
Math库提供对浮点数进行四舍五入运算的方法Math.round(),该方法返回的数据类型为long类型
如果试图将一个数值从一种类型强制转换为另一种类型,而又超出了目标类型的表示范围,结果就会截断成一个完全不同的值
&&和||运算符是按照**"短路"**方式来求值的:如果第一个操作数已经能够确定表达式的值,第二个操作数就不必计算
- 在一些特殊的题目中,这个特性会用来替代循环(比如作为递归的判断条件)
位运算符
- 处理整型类型时,可以直接对组成整数的各个位完成操作,这意味可以使用掩码技术得到整数中的各个位。位运算符包括:&(and),|(or),^(xor),-(not)
- ">>"和"<<"运算符可以将位模式左移或右移,需要建立位模式来完成位掩码的时候,这两个运算符会很方便;">>>"运算符会使用0填充高位
- 移位运算符的右操作数(位于运算符右边的数字)需要完成模32(25)的运算;若左操作数为long类型,则需要对右操作数模64(26)
运算符 结合性 [].() (方法调用) 从左向右 !,~,++,--,+(一元运算),-(一元运算),(),new,()(强制类型转换) 从右向左 *,/,% 从左向右 +,- 从左向右 <<,>>,>>> 从左向右 <,<=,>,>=,instanceof 从左向右 ==,!= 从左向右 & 从左向右 ^ 从左向右 | 从左向右 && 从左向右 || 从左向右 ?: 从右向左 =,+=,-=,*=,/=,%=,&=,|=,^=,<<=,>>=,>>>= 从右向左 **一元运算:**从一个已知数中得出新的数,+-号的一元运算指的是其正负号的职能
**结合性:**决定同级的运算符的运算方向,如从左向右结合性的运算符,运算的时候顺序是从左向右运算
4.字符串
4.1 字符串特性
String类中没有提供修改字符串中某个字符的方法;由于这一特性,String类对象被称为是不可变的;不可变字符串有一个优点:编辑器可以让字符串共享,这一点优化了性能
- 如果复制一个字符串变量,原始字符串与复制字符串共享相同的字符
检查字符串之间是否相等可以使用equals方法,而如果要忽略大小写则可以使用equalsIgnoreCase
"Hello".equalsIgnoreCase("hello"); //不要使用"=="比较字符串!
4.2 字符串相关方法
- substring()
String类的substring方法可以从一个较大的字符串中提取出一个子串
String greeting = "Hello";
String s = greeting.substring(0,3); //s为"Hel"
//substring方法的第二个参数是不想复制的第一个位置,以上个例子为例,在substring中从0开始计数,直到3为止,但不包含3
- join()
如果需要把多个字符串放在一起,并用一个界定符分隔,可以使用静态join方法
String all = String.join("/","S","M","L","XL"); //all为"S/M/L/XL"
repeat()
String repeated = "Java".repeat(3); //repeated为"JavaJavaJava"
StringBuilder()
有时候需要使用较短的字符串构建字符串,传统的字符串拼接方式效率比较低,此时可以使用StringBuilder,在构建完后使用toString方法返回一个String对象即可
StringBuilder builder = new StringBuilder(); builder.append(ch); //拼接一个字符 builder.append(str); //拼接一个字符串 String compString = builder.toString();
4.3 代码单元
字符串由char值序列组成,这意味着码点和代码单元的知识点也可以应用于字符串中;length方法将返回采用UTF-16编码表示给定字符串所需要的代码单元数量;想要得到码点数量(实际的长度)。可以使用codePointCount()方法
String greeting = "Hello"; int n = greeting.length(); //n == 5 int cpCount = greeting.codePointCount(0,greeting.length());
调用s.charAt(n)将返回位置n的代码单元,想要得到第i个码点,可以使用codePointAt(n)
char first = greeting.charAt(0); int index = greeting.offsetByCodePoints(0,1); int cp = greeting.charAt(index);
如此关注码点和代码单元,是因为如果字符串中存在辅助字符,可能会造成charAt()等方法的出错,因为这类字符占用了两个代码单元。为了避免这一问题,尽量不要使用char类型
5.输入与输出
5.1 Scanner相关
若要读取用户的输入,首先需要新建一个对象,然后再指定接收的类型;若要读取一整行输入,可以使用nextLine();若要读取一个单词,可以使用next();读取整数使用nextInt(),读取浮点数使用nextDouble
Scanner in = new Scanner(System.in); String name = in.nextLine(); String firstName = in.next(); int age = in.nextInt();
因为输入是可见的,Scanner类不适合从控制台读取密码(因为可能会暴露用户信息),可以使用Console类来实现
Console cons = System.console(); String username = cons.readLine("user name:"); char[] password = cons.readPassword("Password:");
为了安全起见,返回的密码应该存放在一个字符数组中,而不是字符串中。在对密码处理完成后,应该马上用一个填充覆盖数组元素。Console对象只能每次读取一行输入,而没有能读取单个单词或者数值的方法
Console API相关:
static char[] readPassword(String prompt, Object ... args)
static String readLine(String prompt, Object ... args)
显示字符串prompt(提示符)并读取用户输入,直到输入行结束。args可以用来提供格式参数
Scanner API相关:
boolean hasNext():检测输入中是否还有其它单词
boolean hasNextInt(),boolean hasNextDouble():检测是否还有下一个表示整数或浮点数的字符序列
5.2 格式化字符串(printf)相关
Java沿用了C语言的printf方法来格式化数值
System.out.printf("8.2%f",x);
转换符 | 类型 |
---|---|
d | 十进制整数 |
x | 十六进制整数 |
o | 八进制整数 |
f | 定点浮点数 |
e | 指数浮点数 |
g | 通用浮点数 |
a | 十六进制浮点数 |
s | 字符串 |
c | 字符 |
b | 布尔 |
h | 散列码 |
tx/Tx | 日期时间 |
% | 百分号 |
n | 与平台有关的行分隔符 |
此外,可以指定控制格式化输出外观的各种标志
System.out.printf(""%.2f",10000.0/3.0); //打印3,333.33
标志 | 目的 |
---|---|
+ | 打印正数和负数的符号 |
空格 | 在正数之前添加空格 |
0 | 数字前面补0 |
- | 左对齐 |
( | 将负数括在括号内 |
, | 添加分组分隔符 |
#(对于f格式) | 包含小数点 |
#(对于x或0格式) | 添加前缀0x或0 |
$ | 指定要格式化的参数索引(具体解释见P58) |
< | 格式化前面说明的数值(具体解释见P58) |
注意事项:
参数索引值从1开始,而非从0开始
可以使用s转换符格式化任意的对象。对于实现了Formattable接口的对象,将调用这个对象的formatTo方法;对于未实现这个接口的对象,将调用toString方法将这个对象转换为字符串
可以使用静态的String.format方法创建一个格式化的字符串,而不打印输出
String message = String.format("Hello, %s",name);
对于日期与时间的格式化,应当使用java.time包中的方法,而不是过时的格式化语句
格式说明符语法图:
5.3 文件的输入与输出
读取一个文件也需要构造一个Scanner对象
Scanner in = new Scanner(Path.of("mylife.txt"),StandardCharsets.UTF_8);
如果路径中包含反斜杠符号,就需要在每个反斜杠符号前面额外加一个反斜杠
- 例:Path.of("C:\\myfile.txt");
上述例子中制定了UTF_8编码,如果省略字符编码,则会使用运行这个Java程序的机器的"默认编码",这可能带来兼容性之类的问题。因此,读取文件前最好先知道文件的编码类型
写入文件需要构造一个PrintWriter对象,构造器中需要提供文件名和字符编码
PrintWriter out = new PrintWriter("myfile.txt",StandardCharsets.UTF_8); //若文件不存在,则会自动创建该文件
当指定一个相对文件名(相对路径)时,文件位于Java虚拟机启动目录的位置。如果在命令行下执行以下命令启动程序:java MyProg,启动目录就是命令解释器的当前目录(cmd的当前运行目录)
如果使用IDE,那么启动目录由IDE决定,可以使用下列代码找到目录位置:
String dir = System.getProperty("user.dir");
嫌麻烦也可以使用绝对路径,但兼容性较差
6.流程控制
switch-case中case的标签可以是:类型为char,byte,short,int的常量表达式;枚举常量;字符串字面量
Java提供了一种带标签的break/continue语句,类似于goto可以跳出多层嵌套的语句,非常方便
public void labelTest(){ Scanner in = new Scanner(System.in); int n; read_data: //标签 while(true) { for(;true;) { System.out.print("Enter a number >= 0:"); n = in.nextInt(); if(n < 0){ //如果n<0则break出外层循环 break read_data; } } } System.out.println("Break Success!!"); } //事实上标签可以应用于几乎任何的语句块,可以方便的跳出(但不能跳入)。但并不提倡过多的使用这个特性
注意事项
在C中,允许在嵌套的块中定义一个重名变量,在内层定义的变量会覆盖在外层定义的变量。而在Java中不允许这么做
for语句的3个部分应该对同一个计数器变量进行初始化,检测和更新。若不遵守这一规则,编写的循环常常晦涩难懂
在循环中,检测两个浮点数是否相等需要额外小心
- 例如这个for循环:for(double x = 0;x != 10;x+=0.1),由于舍入的误差,可能永远达不到精确的最终值从而一直循环下去。在这个循环中,由于0.1无法精确的用二进制表示,所以x将从9.999...跳到10.099...
7.大数
浮点数值不适用于无法接受舍入误差的金融计算。如果在数值计算中不允许有任何舍入误差,就应该使用BigDecimal类
如果基本的整数和浮点数精度不能够满足要求,那么可以使用Math包中的两个类:BigInteger和BigDecimal,这两个类可以处理包含任意长度数字序列的数值。BigInteger类实现任意精度的整数运算;BigDecimal可以实现任意精度的浮点数计算
使用静态的valueOf方法可以将普通的数值转换为大数:
BigInteger a = BigInteger.valueOf(100);
对于更大的数,可以使用一个带字符串参数的构造器:BigInteger("25554648464846468468468");
不能使用算术运算符(+或*)处理大数,而需要使用大数类提供的add和multiply方法
8.数组
定义数组
- 在Java中,提供了一种创建数组对象并同时提供初始值的简写形式。这个语法中不需要使用new,甚至不用指定长度
- 例:int[] smallPrimes = {2,3,5,6,8};
- 使用初始化数组时,最后一个值后面允许有逗号,这样方便以后手动添加新代码
- 在Java中允许有长度为0数组;但长度为0的数组与null并不同
- for(variable : collection) statement;foreach循环定义一个变量用于暂存集合中的每一个元素,并执行相应的语句(statement)。collection这一集合表达式必须是一个数组或者是实现了Iterable接口的类对象
- 在Java中,提供了一种创建数组对象并同时提供初始值的简写形式。这个语法中不需要使用new,甚至不用指定长度
打印数组
使用Arrays.toString(a),可以快速简单的打印数组中的元素
foreach循环语句不能自动处理二维数组的每一个元素。要想访问二位数组a的所有元素,需要使用两个嵌套的循环
例:for(double[] row : a)
for(double value : row)
想要快速的打印一个二位数组的数据元素列表,可以调用:Arrays.deeptoString(a)
拷贝数组
直接使用“=”进行拷贝,两个变量将引用同一个数组
int[] luckyNumbers = smallPrimes; luckyNumbers[5] = 12; //现在smallPrimes[5]也等于12
如果希望将一个数组的所有值拷贝到一个新的数组中去,就要使用Arrays类的copyOf方法
int copied = Arrays.copyOf(luckyNumbers,luckyNumbers.length); //第二个参数是新数组的长度,这个方法也可以用来调整数组的大小 luckyNumbers = Arrays.copyOf(luckNumbers,luckNumbers.length * 2);
与C类似,Java的应用程序也可以在运行开始时添加命令行参数
例:java Message -g cruel world
args数组将会包含以下内容:args[0]:"-g"; args[1]:"cruel"; args[2]:"world";
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)