Java学习记录:java的基本程序设计结构(一)
public class test {
public static void main(String[] args) {
}
}
首先,java区分大小写。如果把关键字打错了,程序会运行错误。
其次,java定义类、变量、函数等都不得使用java保留字。
关键字public(公众的)称为访问修饰符,这些修饰符用于控制程序的其他部分对这段代码的访问级别。(第五章会更详细地介绍访问修饰符的有关内容。)
关键字class表明java程序中的全部内容都包含在类中。(下一章会详细讲,现在只需要将类看作程序逻辑的一个容器,定义了应用程序的行为。类是所有java应用的构建模块。Java程序中的所有内容都必须放在类中)。
关键字class后面紧跟类名。Java中定义类名的规则很宽松。类名必须以字母开头,后面可以跟字母和数字的任意组合。长度基本上没限制。
标准命名约定为:类名是以大写字母开头的名词。如果名字由多个单词组成,每个单词的第一个字母都应该大写。这种在一个单词中间使用大写字母的方式有时称为骆驼命名法(camel case)。以其自身为例,应该写为CamelCase。
源代码的文件名必须与公共类的类名相同,并用.Java作为扩展名。
(再次提醒:大小写很重要)
如果已经正确地命名文件,并且源代码中没有任何录入错误,在编译这个源代码之后,会得到一个包含这个类字节码的文件。Java编译器将这个字节码文件自动地命名为类名.class,并存储在源文件所在地同一个目录下。最后,使用下面这个命令运行这个程序:java 类名
(请记住,不要加.class扩展名。)程序执行之后,控制台上将会运行这个程序。
当使用这个命令:java ClassName运行一个已编译的程序时,Java虚拟机总是从指定类中main方法的代码开始执行(“方法”就是“函数”),因此为了能够执行代码,类的源代码中必须包含一个main方法。当然,也可以将你自己的方法添加到类中,并从main方法调用这些方法(详情请见第四章)
【根据java语言规范,main方法必须声明为public】
在java中,用大括号划分程序的各个部分(俗称“块”)。Java中任何方法都必须以“{”开始,以“}”结束。(注:java解释器会忽略空白符)
我们暂且不考虑关键字static void,只把它们当作编译java程序必要的部分就行了(到第四章一切揭晓)现在需要记住的是,每个java应用都必须有一个main方法。
点号(.)用于调用方法。Java的通用语法是:Object.method(parameters)这等价于一个函数调用。
Java使用双引号界定字符串。
Java的方法可以没有参数,也可以有一个或多个参数。即使一个方法没有参数,也需要使用空括号。(实参argument)
System.out还有一个print方法,它不在输出之后增加换行符。println方法在输出之后增加换行符。
在java中由3种标记注释的方法:
1. 使用//(双斜杠)表示从//开始到本行结尾都是注释。
2. /*和*/注释界定符将一段比较长的注释括起来。从/*到*/都是注释。
3. 这一种可以用来自动生成文档。这种注释以/**开始,*/结束。(详情见第四章)
【警告:在java中,/**/注释不能嵌套。也就是不能简单地把代码用/*和*/括起来作为注释,因为这段代码本身可能包含一个*/界定符。】
java是一种强类型语言。这就意味这必须为每一个变量声明一个类型。在java中,一共有8种基本类型(primitive type),其中有4种整型、2种浮点类型、1种字符类型char(用于表示Unicode编码地代码单元)和1种用于表示真值地boolean类型。(注释:java有一个能够表示任意精度的算术包,所谓的“大数”(big number)是java对象,而不是一个基本java类型。)
整型用于表示没有小数部分的数,可以是负数。Java提供了4种整型。
类型int存储需求4b取值范围-2147483648~2147483647
类型short存储需求2b取值范围-32768~32767
类型long存储需求8b取值范围-9223372036854775808~9223372036854775807
类型byte存储需求1b取值范围-128~127
Byte和short类型主要用于特定的应用场合,例如,底层的文件处理或者存储空间有限时的大数组。
在java中,整型的范围与运行java代码的机器无关。Java的各种数据类型的取值范围是固定的。
长整型数值有一个后缀L或1(如4000000000L)。十六进制数值有一个前缀0x或0X(如0xCaFE)。八进制有一个前缀0(例如,010对应十进制中的8)。显然,八进制表示法比较容易混淆,所以很少有程序员使用八进制常数。加上前缀0b或0B可以写二进制数。例如0b1001是9。
另外,可以为数字字面量加下划线,如用1_000_000(或0b1111_0100_0010_0100_0000)表示100万。这些下划线只是为了让人更易读。Java编译器会去除这些下划线。
(注:java没有无符合(unsigned)形式的int、long、short或byte类型。)
(注:如果使用不可能为负的整数值而且确实需要额外的一位(bit),也可以把有符号整数值解释为无符号数,但是要非常仔细。例如,一个byte值b可以不表示-128~127的范围,如果你想表示0~255的范围,也可以存储在一个byte中。基于二进制算术运算的性质,只要不溢出,加法、减法、乘法都能正常计算。但对于其他运算,需要调用Byte.toUnsignedInt(b)来得到一个0~255的int值,然后处理这个整数值,再把它转换回byte。Integer和Long类都提供了处理无符号除法和求余数的方法。)
浮点类型用于表示有小数部分的数值。在java中有两种浮点类型。
类型float存储需求4b取值范围大约±3.40282347e38(6~7位有效数字)
类型double存储需求8b取值范围大约±1.79769313486231570e308(15位有效数字)
double表示这种类型的数值精度是float类型的两倍(有人称之为双精度数(double precision)。很多情况下,float类型的精度(6~7位有效数字)都不能满足需求。实际上,只有很少的情况适合使用float类型,例如,所使用的库需要单精度数,或者需要存储大量单精度数时。
float类型的数值有一个后缀F或f(例如,3.14F)。没有后缀F的浮点数值(如3.14)总是默认为double类型。可选地,也可以在double数值后面添加后缀D或d(例如,3.14D)。
【注:可以使用十六进制表示浮点数字面量。例如,0.125 = 2^(-3)可以写成0x1.0p-3。在十六进制表示法中,使用p表示指数,而不是e。(e是一个十六进制数位。)注意,尾数采用十六进制,指数采用十六进制,指数采用十进制。指数的基数是2,而不是10.】
所有的浮点数计算都遵循IEEE 754规范。具体来说,有3个特殊的浮点数值表示溢出和出错情况:正无穷大、负无穷大、NaN(不是一个数)
例如:一个正整数除以0的结果为正无穷大。计算0/0或者负数的平方根结果为NaN。
【常量Double(或Float).POSITIVE_INFINITY表示正无穷大、常量Double(或Float).NEGATIVE_INFINITY表示负无穷大、常量Double(或Float).NaN表示NaN(不是一个数)。
但是在实际中很少用到它们。特别要说明的是,不能如下检测一个特定结果是否等于Double(Float).NaN:
if(x == Double.NaN)//is never true
所有NaN的值都认为是不相同的。不过,可以使用Double.isNaN方法来判断:
if(x == Double.isNaN)//check whether x is "not a number"
】
【警告:浮点数值不适合用于无法接受舍入误差的金融计算。例如,命令System.out.println(2.0-1.1)将打印出0.8999999999999999,而不是我们期待的0.9。这种舍入误差的主要原因时浮点数值采用二进制表示,而在二进制系统中无法精确地表示分数1/10.这就好像十进制无法精确地表示分数1/3一样。如果需要精确的数值计算,不允许有舍入误差,则应该使用BIgDecimal(decimal:小数、十进制、十进位)类,本章稍后将介绍这个类。】
char类型原本用于表示单个字符。不过,现在情况已经有所改变。如今,有些Unicode字符可以用一个char值描述,另一些Unicode字符则需要两个char值。(详细信息请阅读下一节)
char类型的字面量值要用单引号括起来。例如:‘A’是编码值为65的字符常量。它与“A”不同,“A”是包含一个字符的字符串。char类型的值可以表示为十六进制值,其范围从\u0000~\uFFFF例如,\u2122表示商标符号(™),\u03C0表示希腊字母π。
除了转义序列\u之外,还有一些用于表示特殊字符的转义序列。可以在加引号的字符字面量或字符串中使用这些转义序列。例如,‘\u2122’或“hello\n”。转义序列\u还可以在加引号字符常量或字符串之外使用(而其他所有转义序列不可以)。例如:
public static void main(String\u005B\u005D arges)
就是完全合法的,\u005B表示“[”;\u005D表示“]”
部分特殊字符的转义序列:
\b(退格)【\u0008】
\t(制表)【\u0009】
\n(换行)【\u000a】
\r(回车)【\u000d】
\f(换页)【\u000c】
\”(双引号)【\u0022】
\’(单引号)【\u0027】
\\(反斜线)【\u005c】
\s(空格。在文本块中用来保留末尾空白符)【\u0020】
\newline(只在文本块中使用:连接这一行和下一行)
【警告:Unicode转义序列会在解析代码之前处理。例如:
"\u0022+\u0022"
并不是一个由引号(U+0022)包围加号构成的字符串。实际上,\u0022会在解析之前转换为“,这会得到””+””,也就是一个空串。
更隐秘地,一定要当心注释中的\u。以下注释:
// \u000A is a newline
会产生语法错误,因为读程序时\u000A会替换为一个换行符。类似地,下面这个注释
// look inside c:\users
也会产生一个语法错误,因为\u后面并没有跟着4位十六进制数。
码点是指与一个编码表中的某个字符对应的代码值。
在Unicode标准中,码点采用十六进制书写,并加上前缀U+,例如U+0041就是拉丁字母A的码点。
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);而辅助字符编码为一对连续的代码单元。采用这种编码对表示的每个值都属于基本多语言平面中未使用的2048个值范围,通常称为替代区域(surrogatae area)(U+D800~U+D8FF用于第一个代码单元,U+DC00~U+DFFF用于第二个代码单元。)
这样设计十分巧妙,因为可以很快知道一个代码单元使一个字符的编码还是一个辅助字符的第一或第二部分。例如:是八元数集(http://math.urc.edu/home/baez/octonions)的数学符号,码点为U+1D546,编码为两个代码单元U+D835和U+DD46.(关于编码算法的具体描述见http://tools.ietf.org/html/rfc2781。)
在java中,char类型描述了采用UTF-16编码的一个代码单元。
强烈建议不要在程序中使用char类型,除非确实需要处理UTF-16代码单元。最好将字符串作为抽象数据类型处理(有关内容见下文。)
布尔(boolean)类型有两个值:false和true,用来判定逻辑条件。整型值和布尔值之间不能进行相互转换。
[在C/C++中,数值甚至指针可以代替布尔值。值0相当于布尔值false,非0值相当于布尔值true。在java中则不是这样。因此,java程序员不会遇到以下麻烦:if(x = 0)//oops.....meant x == 0
在C/C++中这个测试可以编译运行,其结果总是false。而在java中,这个测试将不能通过编译,其原因是整数表达式x = 0不能转换为布尔值。]
在java中,每个变量都有一个类型(type)。声明一个变量时,先指定变量的类型,然后是变量名。
注意每个声明都以分号结束。由于声明是一个完整的java语句,而所有java语句都以分号结束,所以这里的分号是必须的。
作为变量名(以及其他名字)的标识符由字母、数字、货币符号以及“标点连接符”组成。第一个字符不能是数字。
‘+’和‘@’之类的符号不能出现在变量名中,空格也不行。字母区分大小写:main和Main是不同的标识符。标识符的长度基本上没有限制。
与大多数程序设计语言相比,java中“字母”“数字”和“货币符号”的范围更大。字母是指一种语言中表示字母的任何Unicode字符。类似地数字包括‘0’~‘9’和表示一位数字的任何Unicode字符。货币符号为¥、$等。标点连接符包括下划线字符”_”、波浪线(“~”?)以及其他一些符号。实际上大多数程序员都总是使用A-Z、a-z、0~9和下划线“_”
【提示:如果想要知道标识符中可以使用哪些Unicode字符,可以使用Character类的isjavaIdentifierStart和isJavaIdentifierPart方法来检查。
提示:尽管$是一个合法的标识符字符,但不要在你自己的代码中使用这个字符。它只用于java编译器或其他工具生成的名字。】
另外,不能使用java关键字作为变量名。
在java9起,单下划线“_”是一个保留字。(在将来的版本可能使用_作为通配符。P.s反正现在是用不了。)
可以在一行中声明多个变量:
int i, j;//both are integers
不过,不提倡使用这种风格。分别声明每一个变量可以提高程序的可读性。
声明一个变量之后,必须用赋值语句显式地初始化变量。否则将报错。
Java中可以将声明放在代码中的任何地方。
在java中,变量的声明要尽可能靠近第一次使用这个变量的地方,这是一种很好的编程风格。
【注释:从java10开始,对于局部变量,如果可以从变量的初始值推断出它的类型就不再需要声明类型。只需使用关键字var而无须指定类型:
var vacationDays = 12;//vacationDays is an int
var greeting = "hello";//greeting is a String
下一章会看到,这个特性可以让对象声明更为简洁(耗时会多吗?)】
在java中,可以用关键字final指示常量。例如:
final double CM_PER_INCH = 2.54
关键字final表示这个变量只能被赋值一次。一旦赋值,就不能再更改了。习惯上,常量名使用全大写。
在Java中,可能经常需要创建一个常量以便在一个类的多个方法中使用,通常将这些常量称为类常量(class constant)。可以使用关键字static final设置一个类常量。下面是使用类常量的一个例子:
public class haihaihai {
public static final double apex = 3.1415926;
public static void main(String[] args){
System.out.println(apex);
}
}
需要注意,类常量的定义位于main方法之外。这样一来,同一类的其他方法也可以使用这个常量。另外,如果一个常量被声明为public(如这个例子中所示),那么其他类的方法也可以使用这个常量。对于这个例子,其他类可以通过haihaihai.apex使用这个类常量。
枚举类型:有时候,一个变量只包含有限的一组值。例如,销售的服装或比萨只有小、中、大和超大这四种尺寸。当然可以将这些尺寸分别编码为整数1、2、3、4或字符S、M、L、X。但这种设置很容易出错。很可能在变量中保存一个错误的值(如0或m)
针对这种情况,可以自定义枚举类型(enumerated type)。枚举类型包括有限个命名值。例如,
enum Size { SMALL, MEDIUM, LARGE, EXTRA_LARGE }
现在,可以声明这种类型的变量
Size s = Size.MEDIUM
Size类型的变量只能存储这个类型声明中所列的某个值,或者特殊值null,null表示这个变量没有设置任何值。(枚举类型以后会讲)
注意:整数被零除将产生一个异常,而浮点数被零除将会得到一个无穷大或NaN结果。
【注意:可移植性是java程序设计语言的设计目标之一。无论是哪个虚拟机上运行,同一运算应该得到同样的结果。对于浮点数的算术运算,实现这样的可移植性是相当困难的。Double类型使用64位存储一个数值,而有些处理器使用80位浮点寄存器。这些寄存器增加了中间步骤的计算精度。
不过,结果可能与始终使用64位计算的结果不一样。为了提高可移植性,java虚拟机的最初规范规定所有的中间计算都必须完成截断。这种做法遭到了数值社区的反对。常用处理器上的截断操作会耗费时间,所以计算速度会减慢。因此,java程序设计语言认识到最优性能与理想的可再生性之间存在冲突,并做出了相应改进。允许虚拟机设计者对中间计算采用扩展精度。不过,用strictfp关键字标记的方法必须使用严格的浮点计算来生成可再生的结果。
如今,处理器已经非常灵活了,它们可以高效地完成64位运算。在java17中,再次要求虚拟机完成严格的64位运算,strictfp关键字现在已经过时了。】
Math类中包含你可能会用到的各种数学函数,这取决于你要编写什么类型的程序。
【注释:println方法和sqrt方法有一个微小的差异。println方法处理System.out对象,而Math类中的sqrt方法并不处理任何对象,这样的方法被称为静态方法(static method)。有关静态方法的详细内容见后文】
在java中,没有完成幂运算的运算符,因此必须使用Math类的pow方法。Pow方法有两个double类型的参数,其返回结果也为double类型。
floorMod方法是为了解决一个长期存在的有关整数余数的问题。下面考虑这样一个问题:计算一个时钟时针的位置。这里要做一个调整,你想归一化为一个0~11之间的数。这很简单:(position+adjustment)%12。不过,如果这个调整为负数会怎么样呢?你可能会得到一个负数。所以要引入一个分支,或者使用((position+adjustment)%12+12)%12.不过怎样都很麻烦。
floorMod方法就让这个问题变得容易了:floorMod(position+adjustment,12)总会得到一个0~11之间的数(遗憾的是,对于负除数,floorMod会得到负数结果,不过这种情况在实际中不常出现。):
int x = 9;
int a = -4;
int y = Math.floorMod(x,a);
结果是-3
————————————————————————
int x = -9;
int a = 4;
int y = Math.floorMod(x,a);
结果是3
Math类提供了一些常用的三角函数:
Math.sin
Math.cos
Math.tan
Math.atan
Math.atan2
还提供了指数函数以及它的反函数——自然对数和以10为底的对数:
Math.exp(指数函数)
Math.log(自然对数)
Math.log10
最后,还提供了两个常量来表示π和e常量最接近的近似值:
Math.PI
Math.E
【提示:不必在数学方法名和常量名前添加前缀“Math”,只要在源文件最上面加上下面这行代码就可以了。
Import static java.lang.Math.*;
静态导入以后会讲】
【注释:在Math类中,为了达到最佳的性能,所有的方法都是用计算机浮点单元中的例程。如果得到一个完全可预测的结果比运行速度更重要的话,就应该使用StrictMath类。它实现了“可自由分发数学库(Freely Distributable Math Library, FDLIBM)”(www.netlib.org/fdlibm)的算法,确保在所有平台上得到相同的结果。】
【注释:Math类提供了一些方法使整数运算更安全。如果一个计算溢出,数学运算符只是悄悄地返回错误的结果而不做任何提醒。例如,10亿乘以3(100000000*3)地计算结果将是-1294967296,因为最大地int值也只是刚刚超过20亿。不过,如果调用Math.multiplyExact(100000000*3),就会产生一个异常。你可以捕获这个异常或者让程序终止,而不是允许它给出一个错误地结果然后悄无声息地继续运行。另外,还有一些方法(addExact、subtractExact、incrementExact、decrementExact、negateExact和absExact)也可以正确地处理int和long参数。】
经常需要将一个数值类型转换为另一种数值类型。下面是数值类型之间的合法转换。
byte——》short——》int——》long;
char——》int;
int——》double;
float——》double;
-------------------------------------------------
int--------->float;
long-------->float;
long--------->double;
上面有六个“——》”箭头,表示无信息丢失的转换;另外有三个“------->”箭头,表示可能有精度损失的转换。
例如,123456789是一个大整数,它包含的位数多于float类型所能表示的位数。将这个整数转换为float类型时,数量级是正确的,但是会损失一些精度。(float的小数点位数是6~7位)
int n = 123456789;
float f = n;//f is 1.23456792E8
当用一个二元运算符连接两个值时(例如n+f,n是整数,f是浮点数),先要将两个操作数转换为同一种类型,然后再进行计算。
【1.如果两个操作数中有一个是double类型,另一个操作数就会转换为double类型。2.否则,如果其中一个操作数是float类型,另一个操作数将会转换为float类型。3.否则,如果其中一个操作数是long类型,另一个操作数将会转换为long类型。4.否则,两个操作数都将被转换为int类型。】
在必要的时候,int类型的值将会自动地转换为double类型。但另一方面,有时也需要将double类型转换int类型。在java中,允许进行这种数值转换,不过当然可能会丢失一些信息。这种可能损失信息地转换要通过强制类型转换(cast)来完成。强制类型转换的语法格式是在圆括号中指定想要转换的目标类型,后面紧跟转换的变量名。例如:
double x = 9.997;
int nx = (int)x;
这样,变量nx的值为9,因为强制类型转换通过截断小数部分将浮点值转换为整型。
如果想舍入(round)一个浮点数来得到最接近的整数(大多数情况下,这种操作更有用),可以使用Math.round方法:
double x = 9.997;
int nx = (int)Math.round(x);
现在,变量nx的值为10.调用round时,仍然需要使用强制类型转换(int)。原因是round方法的返回值是long类型,由于存在消息丢失的可能性,所以只有使用显式的强制类型转换才能够将一个long值赋给int类型的变量。
【警告:如果试图将一个数从一种类型强制转换为另一种类型,而又超出了目标类型的表示范围,结果将会截断成一个完全不同的值。例如,(byte)300实际上会得到44.】
【不要在布尔(boolean)类型与任何数值类型之间进行强制转换,这样可以防止发生一些常见的错误。只有极少数的情况下需要将一个boolean值转换为一个数,此时可以使用条件表达式b?1:0.】
x += 4与x = x +4等价(一般来说,要把运算符放在=号左边,如*=或%=)
【警告:如果运算符得到一个值,其类型与左侧操作数的类型不同,就会发生强制类型转换。(将强制转换为与左侧操作数类型相同的类型)例如,如果x是一个int,则以下语句:
x += 3.5;是合法的,将把x设置为(int)(x + 3.5)。】
需要说明,在java中,赋值是一个表达式(expression)。也就是说,它有一个值,具体来讲就是所赋值的那个值。可以使用这个值完成一些操作,例如,可以把它赋给另一个变量。考虑以下语句:
int x = 1;
int y = x += 4;
x += 4的值是5,因为这是赋给x的值。然后将这个值赋给y。
很多程序员发现这种嵌套赋值很容易混淆。他们更喜欢分别清楚地写出这些赋值,如下所示:
nt x = 1;
x += 4;
int y = x;
自增与自减运算符:由于这些运算符会改变变量的值,所以不能对数值本身应用这些运算符。
这些运算符又两种形式:n++(n--);++n(--n)。后缀和前缀形式都会使变量值加1或减1,但是用在表达式中时前缀形式会先完成加1;而后缀形式会使用变量原来的值:
int m = 7;
int n = 7;
int a = 2* ++n;// now a is 16, m is 8
int b = 2* n++;// now b is 14, n is 8
Java包含丰富的关系运算符。
要检查相等性,可以使用“==”;
要检查不相等性,可以使用“!=”;
还有经常使用的 <(小于)、>(大于)、<=(小于等于)和 >=(大于等于)运算符。
&&表示逻辑“与”(且);
||表示逻辑“或”;
!是逻辑“非”运算符。
&&和||运算符是按照“短路”方式来求值的:如果第一个操作数已经能够确定表达式的值,第二个操作数就不必计算了。
如果用&&运算符结合两个表达式,expression1&&expression2
而且已经计算得到第一个表达式的真值为false,那么结果就不可能是true。因此,第二个表达式就不必计算了。可以利用这这种行为来避免错误。例如,在下面的表达式中:
x != 0 && 1/x > x + y //no division by 0
如果x等于0,那么第二部分就不会计算。因此,如果x为0,也就不会计算1/x,就不会出现除以0的错误。
类似地,如果第一个表达式为true,expression1||expression2的值就自动为true,而无须计算第二个表达式。
条件运算符:java提供了conditional?:运算符,可以根据一个布尔表达式选择一个值。如果条件(condition)为true,表达式
condition ? expression1 : expression2
就计算为第一个表达式的值,否则为第二个表达式的值。例如,
x < y ? x : y
会返回x和y中较小的一个。
switch表达式:需要在两个以上的值中做出选择时,可以使用switch表达式(java14引入)如下所示:
String seasonName = switch (seasonCode)
{
case 0 ->"Spring";
case 1 ->"Summer";
case 2 ->"Fall";
case 3 ->"Winter";
default ->"???";
};
case标签还可以是字符串或枚举类型常量。
【注释:与所有表达式类似,switch表达式也有一个值。注意各个分支中箭头”->”放在值前面。】
【在java14中,switch有4种形式。这只是最有用的一种,剩下的以后会讲】
可以为各个case提供多个标签,用逗号分隔:
int numLetters = switch (seasonName)
{
case "Spring", "Summer", "Winter" -> 6;
case "Fall" -> 4;
default -> -1;
};
switch表达式中使用枚举类型时,不需要为各个标签提供枚举名,这可以从switch值推导得出。例如:enum Size { SMALL, MEDIUM, LARGE, EXTRA_LARGE};
enum Size {SMALL, MEDIUM, LARGE, EXTRA_LARGE};
...
Size itemSize = ...;
String label = switch (itemSize)
{
case SMALL -> "S";// no need to use Size.SMALL
case MEDIUM -> "M";
case LARGE -> "l";
case EXTRA_LARGE -> "XL";
};
在这个例子中,完全可以省略default,因为每一个可能的值都有相应的一个case。
【警告:使用整数或String操作数的switch表达式必须有一个defalut,因为不论操作数值是什么,这个表达式都必须生成一个值。】
【警告:如果操作数为null,会抛出一个NullPointerException】
位运算符:处理整型类型时,还有一些运算符可以直接处理组成整数的各个位。这意味着可以使用掩码技术得到一个数中的各个位。【这些是在二进制水平】位运算符包括:
&("and") |("or") ^("xor") ~("not")
这些运算符按位模式操作。例如,如果n是一个整数变量,而且n的二进制表示中从右边数第4位为1,则
int fourthBitFromRight = (n & 0b1000) / 0b1000;
会返回1,否则返回0。利用&并结合适当的2的幂,可以屏蔽其他位,而只留下其中的某一位。
【注释:应用在布尔值上时,&和|运算符也会得到一个布尔值。这些运算符与&&和||运算符很类似,不过&和|运算符不采用”短路“方法来求值,也就是说,计算结果之前,两个操作数都需要计算。(所以会更耗时?)
另外,还有>>(相当于除以2的幂次方)和<<(相当于乘以2的幂次方)运算符可以将位模式左移或右移。需要建立位模式来完成位掩码时,这两个运算符会很方便:
int fourthBitFromRight = (n & (1 << 3)) >> 3;
最后,>>>运算符会用0填充高位,这与<<不同,>>会用符号位填充高位。不存在<<<运算符
【警告:移位运算符的右操作数要完成模32的运算(除非左操作数是long类型,在这种情况下需要对右操作数完成模64运算)。例如,1<<35的值等同于1<<3或8。】
括号与运算符级别:如果不使用圆括号,就按照这里给出的运算符优先级次序进行计算。
同一个级别的运算符按照从左到右的次序进行计算(但右结合运算符除外,如表所示)。例如,由于&&的优先级比||的优先级高,所以表达式:
a && b || c
等价于:
(a && b) || c
因为 +=是右结合运算符,所以表达式:
a += b += c
等价于:
a += (b += c)
也就是将b += c的结果(完成加法后b的值)加到a上。
【与C/C++不同,java不使用逗号运算符。不过,可以在for语句的第1和第3部分中使用逗号分隔表达式列表。】
优先级
运算符
简介
结合性
1
[ ]、 .、 ( )
方法调用,属性获取
从左向右
2
!、~、 ++、 --
一元运算符
从右向左
3
* 、/ 、%
乘、除、取模(余数)
从左向右
4
+ 、 -
加减法
从左向右
5
<<、 >>、 >>>
左位移、右位移、无符号右移
从左向右
6
< 、<= 、>、 >=、 instanceof
小于、小于等于、大于、大于等于,
对象类型判断是否属于同类型
从左向右
7
== 、!=
2个值是否相等,2个值是否不等于。
下面有详细的解释
从左向右
8
&
按位与
从左向右
9
^
按位异或
从左向右
10
|
按位或
从左向右
11
&&
短路与
从左向右
12
||
短路或
从左向右
13
?:
条件运算符
从右向左
14
=、 += 、-= 、*= 、/=、 %=、 &=、 |=、 ^=、 <、<= 、>、>= 、>>=
混合赋值运算符
从右向左
字符串:从概念上讲,java字符串就是Unicode字符序列。例如,字符串“java\u2122”由5个Unicode字符j、a、v、a和™组成。Java没有内置的字符串类型,而是标准java类库中提供了一个预定义类,很自然地叫作String。每个用双引号括起来的字符串都是String类的一个实例:
String e = "";// an empty string
String greeting = "Hello";
子串: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.
拼接:java语言允许使用“+”号连接(拼接)两个字符串。
String expletive = "Expletive";
String PG13 = "deleted";
String message = expletive + PG13;
上述代码将字符串“Expletivedeleted”赋给变量message(注意,单词之间没有空格,+号完全按照给定的次序将两个字符串拼接起来)。
当将一个字符串与一个非字符串的值进行拼接时,后者会转换成字符串(以后会看到,任何一个java对象都可以转换成字符串)。例如:
int age = 13;
String rating = "PG" + age;
将把rating设置为“PG13”。
这个特性通常用在输出语句中。例如:
System.out.println("The answer is" + answer);
这是一条合法的语句,会打印出你希望的结果(因为单词is后面加了一个空格,输出时也会有这个空格)。
如果需要把多个字符串放在一起,用一个界定符分隔,可以使用静态join方法:
String all = String.join("/", "s", "m", "l", "xl");// all is the string "s/m/l/xl"
在java11中,还提供了一个repeat方法:
String repeated = “java”.repeat(3);// repeated is "javajavajava"
字符串不可变:
String类没有提供任何方法来修改字符串中的某个字符。如果希望将greeting的内容修改为“Help!”,不能直接将greeting的最后两个位置的字符修改为‘p’和‘!’。在java中这很容易实现。可以提取想要的子串,再将希望替换的字符拼接:
String greeting = "Hello";
greeting = greeting.substring(0,3) + "p!";
上面这条语句将greeting变量的当前值修改为“Help!”。
由于不能修改java字符串中的单个字符,所以在java文档中将String类对象称为是不可变的(immutable),如同数字3永远是数字3一样,字符串“hello”永远包含字符H、e、l、l和o的代码单元序列。你不能修改这些值,不过,可以修改字符串变量greeting的内容,让它指向另外一个字符串,这就如同可以让原本存放3的数值变量改成存放4一样。
这样做难道不会大大降低效率吗?看起来好像修改代码单元要比从头创建一个新字符串更简单。答案是也对,也不对。的确,通过拼接“Hel”和“p!”来生成一个新字符串的效率确实不高。但是,不可变字符串有一个很大的优点:编译器可以让字符串共享。
为了弄清具体如何工作,可以想象各个字符串存放一个在公共存储池中。字符串变量指向存储池中相应的位置了如果复制一个字符串变量,原始字符串和复制的字符串共享相同的字符。
总而言之,java的设计者认为共享带来的高效率远远超过编辑字符串(提取子串和拼接字符串)所带来的低效率。可以看看你自己的程序,你会发现,大多数情况下都不会修改字符串,而只是需要对字符串进行比较。(有一种例外情况,将来自文件或键盘的单个字符或较短字符串组装成更大的字符串。为此,java提供了一个单独的类(以后会讲)
检测字符串是否相等:
可以使用equals方法检测两个字符串是否相等。对于表达式:
s.equals(t)
如果字符串s与字符串t相等,则返回true,否则返回false。需要注意的是,s与t可以是字符串变量,也可以是字符串字面量。例如,以下表达式是合法的:
"Hello".equals(greeting)
要想检测两个字符串是否相等,而不区分大小写,可以使用equalsIgnoreCase方法。
"Hello".equalsIgnoreCase(greeting)
不要使用==运算符检测两个字符串是否相等!这个运算符只能够确定两个字符串是否存放在同一个位置上。当然,如果字符串在同一个位置上,它们必然相等。但是,完全有可能将多个相等的字符串副本存放在不同的位置上。
String greeting = "Hello";// initialize greeting to a string
if(greeting == "Hello")...//probably true
if(greeting.substring(0, 3) == "Hel")...//probably false
如果虚拟机总是共享相等的字符串,则可以使用==运算符检测字符串是否相等。但实际上只有字符串字面量会分享,而+或substring等操作得到的字符串并不共享。因此,千万不要使用==运算符测试字符串的相等性,否则程序中会出现最糟糕的一种bug,这种bug可能会间歇性地随机出现。
【C程序员从不使用==对字符串进行比较,而是使用strcmp函数。Java的compareTo方法就类似于strcmp,因此,可以如下这样使用:
if(greeting.compareTo("Hello") == 0)...
不过,使用equals看起来更为清晰。】
空串与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值上调用方法,会出现错误。
码点与代码单元:
Java字符串是一个char值序列。char数据类型是采用UTF-16编码表示Unicode码点的一个代码单元。最常用的Unicode字符可以用一个代码单元表示,而辅助字符需要一对代码单元表示。
length方法将返回采用UTF-16编码表示给定字符串所需要的代码单元个数。例如:
String greeting = "Hello";
int n = greeting.length();// is 5
要想得到实际长度,即码点个数,可以调用:
int cpCount = greeting.codePoint(0, greeting.length());
调用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'
要想得到第i个码点,可以使用以下语句:
int index = greeting.offsetByCodePoints(0, i);//code point offset(代码点偏移量)
Int cp = greeting.codePointAt(index);
为什么会对代码单元如此大惊小怪?请考虑下面这个句子:
(\uD835\uDD46)is the set of octonions.
使用UTF-16编码表示字符(\uD835\uDD46)(U+1D546)需要两个代码单元。调用:
char ch = sentence.charAt(1)
返回的一个不是一个空格,而是的第二个代码单元。为了避免这个问题,不要使用char类型。这太底层了。
【不要以为可以忽略代码单元在U+FFFF以上的奇怪字符,喜欢emoji表情符号的用户可能会在字符串中加入类似(U+1F37A,啤酒杯)的字符。】
如果想要遍历一个字符串,并且依次查看每一个码点,可以使用以下语句:
int cp = sentence.codePointAt(i);
i += Character.charCount(cp);
Character.charCount()是java中的内置函数,用于确定表示指定字符所需的字符数。 如果字符等于或大于0x10000,则方法返回2,否则返回1。(就是返回一个字符的代码单元)
可以使用以下语句实现反向遍历:
i--;
if(Character,isSurrogate(Sentence(i)))i--;
int cp = sentence.codePointAt(i);
Character.isSurrogate()它的作用是确定给定的char值是否为Unicode low-surrogate代码单元 (也称为trailing-surrogate代码单元)。(就是检查除了第一个代码平台的其他代码平台的第二个代码单元。)
显然,这很麻烦。更容易的办法是使用codePoints方法,它生成int值的一个“流”,每个int值对应一个码点。(流在卷II介绍。)可以将流转换为一个数组(以后会讲),再完成遍历。
int[] codePoints = str.codePoints().toArray();
反之,要把一个码点数组转换为一个字符串,可以使用构造器(以后会讲)
String str = new String(codePoints, 0, codePoints.length);
要把单个码点转换为一个字符串,可以使用Character.toString(int)方法:
int codePoint = 0x1F37A;
str = Character.toString(codePoint);
【虚拟机不一定把字符串实现为代码单元序列。在java9中使用了一个更紧凑的表示。只包含单字节代码单元的字符串使用byte数组实现,所有其他字符串使用char数组。】
Strng API:
java中的String类包含近100个方法。下面的API注释汇总了最常用的一些方法。
这里给出的API注释可以帮助你理解java应用程序编程接口(API)。每一个API注释首先给出类名,如java.lang.String。(java.lang包名的重要性以后会解释。)类名之后是一个或多个方法的名字、解释和参数描述。
API注释不会列出一个特定类的所有方法,而是会以简洁的方法给出最常用的一些方法,完整的方法列表请参见联机文档。
类名后面的编号是引入这个类的JDK版本号。如果某个方法是之后添加的,那么这个方法后面还会给出一个单独的版本号。
Java.lang.String 1.0:
·char(类型)charAt(int(类型)index)
返回给定位置的代码单元。(除非对底层的代码单元感兴趣,否则不需要调用这个方法)
·int codePointAt(int index) 5
返回从给定位置开始的码点。
·int offsetByCodePoints(int startIndex, int cpCount) 5
返回从startIndex码点开始,cpCount个码点后的码点索引。
·int compareTo(String other)
按照字典顺序,如果字符串位于other之前,返回一个负数;如果字符串位于other之后,返回一个正数;如果两个字符串相等,返回0。
·IntSream codePoints() 8
将这个字符串的码点作为一个流返回。调用toArry将它们放在一个数组中。
·new String(int[] codePoints, int offest, int count) 5
用数组中从offset开始的count个码点构造一个字符串。
·boolean isEmpty()
·boolean isBlank() 11
如果字符串为空或者由空白符组成,返回true。
·boolean equals(Object other)
如果字符串与other相等,返回true。
·boolean equalsIgnoreCase(String other)
如果字符串与other相等(忽略大小写),返回true。
·boolean startsWith(Sring 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或cp不在字符串,则返回-1。
·int lastIndexOf(String str)
·int lastIndexOf(String str, int fromIndex)
·int lastIndexOf(int cp)
·int lastIndexOf(int cp, int fromIndex)
返回与字符串str或码点cp相等的最后一个子串的开始位置。从字符串末尾或fromIndex开始匹配。如果str或cp不在字符串中,则返回-1。
·int length()
返回字符串代码单元的个数。
·int codePointCount(int startIndex, int endIndex) 5
返回startIndex到endIndex - 1之间的码点个数。
·String replace(CharSequence oldString, CharSequence newString)
返回一个新字符串,这是用newString替换原始字符串中与oldString匹配的所有子串得到的。可以用String或StringBuilder对象作为CharSequence参数。
·String substring(int beginIndex)
·Sring substring(int beginIndex, int endIndex)
返回一个新字符串,这个字符串包含原始字符串中从beginIndex到字符串末尾或endIndex - 1的所有代码单元。
·String toLowerCase()
·String toUpperCase()
返回一个新字符串,这个字符串包含原始字符串中的所有字符,不过将原始字符串中的大写字母改为小写,或者将原始字符串中的小写字母改成大写字母。
·String strip() 11
·String stripLeading() 11
·String stripTrailing() 11
返回一个新字符串,这个字符串要删除原始字符串头部和尾部或者只是头部或尾部的空白符。要使用这些方法,而不要使用古老的trim方法删除小于等于U+0020的字符。
·String join(ChaSequence delimiter, CharSequence ...... elements) 8
返回一个新字符串,用给定的定界符连接所有元素。
·String repeat(int count) 11
返回一个字符串,将当前字符串重复count次。
【注释:在API注释中,有一些CharSequence类型的参数。这是一种接口类型,所有字符串都属于这个接口。之后将介绍更多有关接口的内容。现在只需知道,当看到一个CharSequence形参(parameter)时,完全可以传入String类型的实参(argument)。】
阅读联机API文档:
正如前面看到的,String类包含许多方法。而且,标准库中有几千个类,方法数量更加惊人。要想记住所有有用的类和方法显然不太可能。因此,学会使用联机API文档十分重要,从中可以查找标准类库的所有类和方法。可以从Oracle下载API文档,并保存在本地。也可以在浏览器中访问http://docs.oracle.com/en/java/javase/17/docs/api。
构建字符串:
有些时候,需要由较短的字符串构建字符串,例如,按键或文件中的单词。如果采用字符串拼接的方式来达到目的,效率会比较低。每次拼接字符串时,都会构建一个新的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();
【注释:StringBuffer类的效率不如StringBuilder类,不过它允许采用多线程的方式添加或删除字符。如果所有字符串编辑操作都在单个线程中执行(通常都是这样),则应当使用StringBuilder类。这两个类的API是一样的。】
下面的API注释包含了StringBuilder类中最重要的方法:
java.lang.StringBuilder 5
·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 offest, String str)
在offest位置插入一个字符串并返回this。
·StringBuilder inset(int offset, char c)
在offest位置插入一个代码单元并返回this。
·StringBuilder delet(int startIndex, int endIndex)
删除从startIndex到endIndex-1的代码单元并返回this。
·String toString()
返回一个字符串,其数据与构建器或缓冲器内容相同。
文本块:
利用java15新增的文本块(text block)特性,可以很容易地提供跨多行的字符串字面量。文本块以”””开头(这是开始”””),后面是一个换行符,并以另一个”””结尾(这是结束”””)
String greeting = """
Hello
world
""";
文本块比相应的字符串字面量更易于读写:
"Hello\nWorld\n"
这个字符串包含两个\n:一个在Hello后面,另一个在World后面。开始”””后面的换行符不作为字符串字面量的一部分。
String prompt = """
Hello, my name is Hal.
Please enter your name:""";
文本块特别适合包含用其他语言编写的代码,如SQL或HTML。可以直接将那些代码粘贴到一对三重引号之间:
String html = """
<div>
Beware of those who say "Hello" to the world
</div>
""";
需要说明的是,一般不用对引号转义。只有两种情况下需要对引号转义:
1·文本块以一个引号结尾。
2·文本块中包含三个或更多引号组成的一个序列。
遗憾的是,所有反斜线都需要转义。
常规字符串中的所有转义序列在文本块中也有同样的作用。
有一个转义序列只能在文本块中使用。行尾的\会把这一行与下一行连接起来。例如:
"""
Hello,my name is Hal,\
Please enter your name:""";
等同于:
"Hello, my name is Hal. Please enter your name:"
文本块会对行结束符进行标准化,删除末尾的空白符,并把Windows的行结束符(\r\n)改为简单的换行符(\n)。尽管不太可能,不过假如确实需要保留末尾的空格,这种情况下可以把最后一个空格转换为一个\s转义序列。
对于前导空白符则更为复杂。考虑一个从左边界缩进的典型的变量声明。文本块也可以缩进:
String html = """
<div>
Beware of those who say "Hello" to the world
</div>
""";
将去除文本块中所有行的公共缩进。实际字符串为:
String html =
"<div>\n Beware of those who say "Hello" to the world</div>\n"
;
注意,第一行和第三行没有缩进。
你的IDE很可能会使用制表符、空格或者制表符以及空格缩进所有文本块。
Java很清晰,它没有规定制表符的宽度。空白符前缀必须与文本块中的所有行完全匹配。
去除缩进过程中不考虑空行。不过,结束”””前面的空白符很重要。一定要缩进到想要去除的空白符的末尾。
【警告:要当心缩进文本块的公共前缀中混用制表符和空格的情况。不小心漏掉一个空格很容易得到一个缩进错误的字符串。】
【提示:如果一个文本块中包含非Java代码,实际上最好沿左边界放置。这样可以与java代码区分开,而且可以为长代码行留出更多空间。】
【学习参考书籍为:《Java核心技术卷I》】