一些小要点
最近深感自己编程功底不牢,大多数东西都大概只会用,问起细节啥的一点答不上,正好可能快考虑实习了,也没啥事干,就从头开始看一下打好些基础吧。
养成良好习惯,查阅官方API文档!
Java的基本程序结构
Java程序
Java程序必须从一个public类的public static void main(注意main是小写!)方法开始
源代码的文件名必须与public类的类名相同(如果有的话),并以.java作为扩展名
编译:javac Example.java
执行:java Example
Java中main方法不会为操作系统返回一个退出码,如果main方法正常退出,那么Java程序的退出码是0(成功运行)。如果需要以其它退出码终止程序,请调用System.exit方法
注释
三种注释方式:
1 | int a = 1; // a is 1 |
第二种不可以嵌套!不能简单将代码用这种方式包围作为注释,因为代码本身可能包含*/
第三种用于自动生成文档,详见第四章
数据类型
整形
java中没有无符号整形,如果需要使用非负整数值且确实需要额外一位,可以将有符号整数值解释为无符号数,由于二进制算术运算的性质,在不溢出的情况下,加减乘都可以正常计算。但对于其他运算,需要先转为有符号数,运算完再转回。Integer和Long类都提供了无符号除法和求余数的方法。
浮点数
根据IEEE754规范,浮点数有3个特殊值:+∞、-∞、NaN。Double和Float类也提供了三个常量用于表示这三个值:POSITIVE_INFINITY
、NEGATIVE_INFINITY
、NaN
。判断时不能直接用等号(所有NaN的值都认为是不同的)
字符
Unicode是一个标准,规定了每个字符对应的代码值(即码点)。而UTF-8、UTF-16等则是不同的编码,决定底层具体如何储存这些字符。Java采用UTF-16可变长度编码,一个字符对应2或4bytes,char的大小为2bytes,一个char可能是一个字符,也可能只是一个字符的一半(单独出来无意义),因此最好不要直接使用char,而是作为字符串来处理。
UTF-8也是可变长度编码,一个字符对应1-4bytes。对于这种可变字符编码,一般代码单元的大小为所有可能字符大小的最大公因数,且要满足前缀码的要求(这样才能在长度不一的情况下唯一识别)。如c语言中使用UTF-8,,char的大小就是1byte。相比于固定长度编码,可变长度编码设计时会麻烦一些,但可以节省空间。
转义序列\u
可以在多个地方使用,包括字符串、字符字面量、标识符(如函数名、变量名)等,用Unicode码点代替文本字符,但不允许用于数字、布尔值等;其他的一些转义序列可以在字符字面量或字符串中代替文本。
public static void main(String\u005B\u005D args)是完全合法的,\u005B\u005D分别代表[
和]
Unicode转义序列会在解析代码前处理
变量与常量
变量
java中不区分声明与定义(所有都是声明),声明后必须显示赋值才能使用。
可以使用Character类的isJavaIdentifierStart和isJavaIdentifierPart方法来检查一个Unicode字符是否可以作为标识符。此外,不能使用Java关键字作为变量名
$是合法的,但不要在代码中使用,它只用于Java编译器或其它工具生成的名字
变量定义后需要显式初始化,若使用未初始化的变量,编译器会报错.
从Java10开始,对于局部变量,如果可以从变量的初始值推出其类型,可以不再声明类型,使用var关键字即可:
1 | var num = 12; // num is an int |
常量
可用final关键字指示常量,表示该变量只能被赋值一次,习惯上,常量名用全大写及下划线表示。
枚举类型
看一下例子简单了解一下,第5章会详细展开
1 | enum Size { SMALL, MIDDLE, LARGE, EXTRA_LARGE }; |
枚举变量只能储存为声明中所列出的值或null
运算
算术运算符
整数除以0会抛出异常,浮点数除以0会返回无穷大或NaN
关于取余%,如果第一个数是负奇数,结果会是-1(大概是因为最早制定规则的人没有想好),因此Java提供了floorMod方法,不过对于负除数,这个方法也无能为力,但这种情况很少出现
数学函数
Math类中提供了常用的三角函数、指对数运算以及一些常量。但这些方法不能保证在所有机器上运行结果一样(为了速度,可能会使用不同cpu各自提供的指令实现),如果需要保证结果一致,请使用StrictMath类。
普通算术运算符溢出不会产生异常,如果想避免这种情况可以用Math.addExact等方法,这些方法会在溢出时抛出异常。
数值转换
如图所示,有损转换需要强制类型转换。
加减乘除时需要转为同样的数据类型再进行,遵循以下规则:
· 如果有一个是double,则全部转为double
· 否则,如果有一个是float,则全部转为float
· 否则,如果有一个是long,则全部转为long
· 否则,全部转为int
+=、*=等运算符会自动帮你完成强制类型转换。
switch表达式
请见3.8,与switch语句一起介绍。
操作数如果为null会抛出异常。
位运算符
即&、|、^、~、<<、>>(用符号位填充)、>>>(用0填充)
当&、|作用在布尔值上时,与&&、||结果一致,但不会短路
移位运算符的右操作数需要完成模32的运算!1<<35实际上是1<<3
运算符优先级
建议直接加括号,别惦记那破优先级了,另外Java中没有逗号运算符
字符串
主要内容大概就是以下这些:
1、字符串字面量相当于是一个固定的引用、对象变量(类似于c语言中const指针),当相关代码运行时,jvm会先检查这个字面量对应的对象是否已经创建,如果未创建,则会先创建对象,并且以后这个字面量都引用这个对象。
2、字符串对象分为两种,一种是字符串字面量引用的对象,这一部分由jvm负责实现,在字符串常量池中储存;而另一部分由前者通过运算产生,这一部分是java代码完成的,储存在堆中。
3、虽然String是内置的,但本质上仍然是一个类,因此,对对象变量调用==时,实际上是比较二者是否引用同一个对象。
4、字符串对象本身是不可变的,我们调用各种方法,实际上是创建一个新的字符串对象(即上述的第二种)。
关于引用、对象变量、对象等说法可以参照第四章理解。
拼接
Java中所有对象都可以转为字符串,直接使用+
来拼接字符串
码点与代码单元
length方法返回的是代码单元个数,如果想要得到实际长度(码点个数),可以使用codePointCount方法。同理有charAt、codePointAt方法,但最好不要使用char,这太底层了。
如果要遍历字符串,更方便且正确的方法是使用codePoints方法,生成一个int流,每个int对应一个码点。可以将流转为数组,码点数组转为字符串;如果只需要将一个码点(int)转为字符串,可以使用Character.toString(int)方法。
1 | int cpCount = s.codePointCount(0, s.length()); |
Java9之前String是用char数组实现的,而这之后使用了更紧凑的表示,把只包含单字节代码单元的字符串用byte数组实现,其它仍使用char数组。
StringBuilder
这个类可以提高效率,避免拼接字符串时每步都构建String对象的问题。创建对象后依次调用append方法,最后调用toString方法即可。
StringBuffer与这个类效果一样,允许多线程。
文本块
可以提供跨多行的字符串字面量,以"""
开头,后面是一个换行符,并以"""
结尾(结尾没有要求换行),比较适合用于SQL、HTML等。
1 | String s = """ |
一般不对引号转义,除非文本块以一个引号结尾或文本块包含三个及以上引号的序列。反斜线\
必须转义,不转义意味着将这一行与下一行连接起来。
结束时会进行标准化,删除末尾空白符(如果需要空白,请用\s
转义序列)并把Windows的行结束符\r\n
改为简单的换行符\n
,去除所有行的公共缩进,不考虑空行(因此结尾”””前的空白很重要,请缩进到要去除的空白符的末尾)。
输入与输出
读取输入
用标准输入流System.in构造一个Scanner对象,随后使用nextInt等函数读取输入。
但这种输入对所有人可见,不适于从控制台读取密码,这种情况可以使用Console类来实现。不过Console类不如Scanner方便,一次必须读入一行,且不提供读取单个单词、数字的方法。
1 | Scanner in = new Scanner(System.in); |
如果有可能进行交互操作,就通过控制台窗口为交互的用户返回一个Console对象,否则返回null。对于任何一个在控制台窗口启动的程序,都可使用Console对象。其他情况是否可用取决于所使用的系统,如Intellij的终端中就不可用。
格式化输出
直接用System.out.print(x)输出,会以x的类型允许的最大非0位数打印x,如10000.0 / 3.0 会输出为 3333.3333333333335。
也可提供多个参数,如 System.out.print(“Hello, %s. Next year, you’ll be %d.”, name, age)
关于以%
开头的格式说明符,具体格式如下:

flag(控制输出外观):

width表示最小宽度,没有设置flag且长度不足时,数字会右对齐,并且在前面填充空格,而字符串会左对齐,并且在后面填充空格
conversion character(表示数值类型):

也可以用静态的String.format方法创建格式化的字符串而不输出。
可以用s转换字符格式化任意对象,如果实现了Formattable接口,会调用formatTo方法,否则调用toString方法。
格式化规则基于本地化环境,比如,在德国,分组分隔符是点号而非逗号。后续会介绍如何控制应用的国际化行为。
文件输入与输出
读取一个文件需要Scanner对象,写入一个文件需要PrintWriter对象,随后像System.in/out一样使用即可。
1 | Scanner in = new Scanner(Path.of("myfile.txt"), StandardCharsets.UTF_8); |
前者需要的是Path对象,因此要调用Path.of方法,当然也可以提供String对象,只不过这样会把这个String当成数据;后者直接提供文件名即可(不存在则创建)。如果文件不存在/无法创建,那么会抛出IOException。
二者都需要提供编码方式(如果不提供则默认为运行这个Java程序的机器的“默认编码”,最好不要这样)。
如果文件名包含反斜线,需要进行转义,如”c:\\mydirectory\\myfile.txt”
如果使用相对路径,文件将相对于启动Java虚拟机的目录,如果使用集成开发环境,那么由IDE控制。可以通过System.getProperty(“user.dir”)来找到这个目录的位置。
控制流程
if、for、while、do while等与c++一致,就省略了喵,至于带标签的break、continue等,建议不要使用,也不做笔记了。
块作用域
块作用域即用大括号{}括起来的部分,确定变量的作用域。
c++中允许在嵌套的块中重定义一个变量,内层遮蔽外层,但Java中不允许这样做。
switch
一共有四种形式,有直通行为是指需要加break,不然会一直往下执行;yield用于返回表达式的值。


switch表达式的关键是生成一个值,或者产生一个异常而失败。因此请注意以下几点:可以在switch表达式的一个分支中抛出异常;不可以使用return、break、continue语句;case必须能涵盖所有情况,或者用default。
大数
即BigInteger和BigDecimal两个类,可以处理包含任意长度数字序列的值。
可以使用对应的valueOf方法将普通的数转为大数,但对于BigDecimal类,更应该用带字符串参数的构造器而不是使用double参数的构造器,因为这可能会产生误差,如0.1无法用二进制有限位表示。
具体的各种运算请参照API文档。
数组
1 | int[] a; // better |
对象变量只是一个引用,如果要拷贝一份新的,需要调用Arrays.copyOf方法
增强的for循环
1 | for (variable : collection) statement |
collection表达式的结果需要是数组或实现了Iterable接口的类对象
命令行参数
Java程序的main方法中,程序名并不储存在args数组中,与c++不同!
排序
对数值型数组进行排序,直接调用Arrays.sort方法即可,使用的是优化的快排算法。
非数值型,需要提供比较器,参考后续部分
多维数组
实际上,Java没有多维数组,只有数组的数组,因此,在内存中不一定是连续的,也可以轻松构造不规则数组。
1 | double[][] b = new double[10][6]; // java |