Java基础整理

  1. 1. 养成良好习惯,查阅官方API文档!
  2. 2. Java的基本程序结构
    1. 2.1. Java程序
    2. 2.2. 注释
    3. 2.3. 数据类型
      1. 2.3.1. 整形
      2. 2.3.2. 浮点数
      3. 2.3.3. 字符
    4. 2.4. 变量与常量
      1. 2.4.1. 变量
      2. 2.4.2. 常量
      3. 2.4.3. 枚举类型
    5. 2.5. 运算
      1. 2.5.1. 算术运算符
      2. 2.5.2. 数学函数
      3. 2.5.3. 数值转换
      4. 2.5.4. switch表达式
      5. 2.5.5. 位运算符
      6. 2.5.6. 运算符优先级
    6. 2.6. 字符串
      1. 2.6.1. 拼接
      2. 2.6.2. 码点与代码单元
      3. 2.6.3. StringBuilder
      4. 2.6.4. 文本块
    7. 2.7. 输入与输出
      1. 2.7.1. 读取输入
      2. 2.7.2. 格式化输出
      3. 2.7.3. 文件输入与输出
    8. 2.8. 控制流程
      1. 2.8.1. 块作用域
      2. 2.8.2. switch
    9. 2.9. 大数
    10. 2.10. 数组
      1. 2.10.1. 增强的for循环
      2. 2.10.2. 命令行参数
      3. 2.10.3. 排序
      4. 2.10.4. 多维数组

一些小要点

最近深感自己编程功底不牢,大多数东西都大概只会用,问起细节啥的一点答不上,正好可能快考虑实习了,也没啥事干,就从头开始看一下打好些基础吧。

养成良好习惯,查阅官方API文档!


Java的基本程序结构

Java程序

Java程序必须从一个public类public static void main(注意main是小写!)方法开始

源代码的文件名必须与public类的类名相同(如果有的话),并以.java作为扩展名

编译:javac Example.java
执行:java Example

Tips

Java中main方法不会为操作系统返回一个退出码,如果main方法正常退出,那么Java程序的退出码是0(成功运行)。如果需要以其它退出码终止程序,请调用System.exit方法

注释

三种注释方式:

1
2
3
4
5
6
7
8
9
10
11
int a = 1; // a is 1

/* do something */
a = 2;

/**
* class A
*/
class A {

}

第二种不可以嵌套!不能简单将代码用这种方式包围作为注释,因为代码本身可能包含*/

第三种用于自动生成文档,详见第四章

数据类型

整形

java中没有无符号整形,如果需要使用非负整数值且确实需要额外一位,可以将有符号整数值解释为无符号数,由于二进制算术运算的性质,在不溢出的情况下,加减乘都可以正常计算。但对于其他运算,需要先转为有符号数,运算完再转回。Integer和Long类都提供了无符号除法和求余数的方法。

浮点数

根据IEEE754规范,浮点数有3个特殊值:+∞、-∞、NaN。Double和Float类也提供了三个常量用于表示这三个值:POSITIVE_INFINITYNEGATIVE_INFINITYNaN。判断时不能直接用等号(所有NaN的值都认为是不同的)

字符

Unicode是一个标准,规定了每个字符对应的代码值(即码点)。而UTF-8、UTF-16等则是不同的编码,决定底层具体如何储存这些字符。Java采用UTF-16可变长度编码,一个字符对应2或4bytes,char的大小为2bytes,一个char可能是一个字符,也可能只是一个字符的一半(单独出来无意义),因此最好不要直接使用char,而是作为字符串来处理。

UTF-8

UTF-8也是可变长度编码,一个字符对应1-4bytes。对于这种可变字符编码,一般代码单元的大小为所有可能字符大小的最大公因数,且要满足前缀码的要求(这样才能在长度不一的情况下唯一识别)。如c语言中使用UTF-8,,char的大小就是1byte。相比于固定长度编码,可变长度编码设计时会麻烦一些,但可以节省空间。

转义序列\u可以在多个地方使用,包括字符串、字符字面量、标识符(如函数名、变量名)等,用Unicode码点代替文本字符,但不允许用于数字、布尔值等;其他的一些转义序列可以在字符字面量或字符串中代替文本。

Example

public static void main(String\u005B\u005D args)是完全合法的,\u005B\u005D分别代表[]

Unicode转义序列会在解析代码前处理

变量与常量

变量

java中不区分声明与定义(所有都是声明),声明后必须显示赋值才能使用。

关于变量名称

可以使用Character类的isJavaIdentifierStart和isJavaIdentifierPart方法来检查一个Unicode字符是否可以作为标识符。此外,不能使用Java关键字作为变量名

$是合法的,但不要在代码中使用,它只用于Java编译器或其它工具生成的名字

变量定义后需要显式初始化,若使用未初始化的变量,编译器会报错.

Tips

从Java10开始,对于局部变量,如果可以从变量的初始值推出其类型,可以不再声明类型,使用var关键字即可:

1
2
var num = 12; // num is an int
var name = "AAA"; // name is a String

常量

可用final关键字指示常量,表示该变量只能被赋值一次,习惯上,常量名用全大写及下划线表示。

枚举类型

看一下例子简单了解一下,第5章会详细展开

1
2
enum Size { SMALL, MIDDLE, LARGE, EXTRA_LARGE };
Size s = Size.SMALL;

枚举变量只能储存为声明中所列出的值或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
2
3
int cpCount = s.codePointCount(0, s.length());
int[] codePoints = s.codePoints().toArray();
String str = new String(codePoints, 0, codePoints.length)

Java9之前String是用char数组实现的,而这之后使用了更紧凑的表示,把只包含单字节代码单元的字符串用byte数组实现,其它仍使用char数组。

StringBuilder

这个类可以提高效率,避免拼接字符串时每步都构建String对象的问题。创建对象后依次调用append方法,最后调用toString方法即可。

StringBuffer与这个类效果一样,允许多线程。

文本块

可以提供跨多行的字符串字面量,以"""开头,后面是一个换行符,并以"""结尾(结尾没有要求换行),比较适合用于SQL、HTML等。

1
2
3
4
String s = """
Hello,
world.
""";

一般不对引号转义,除非文本块以一个引号结尾或文本块包含三个及以上引号的序列。反斜线\必须转义,不转义意味着将这一行与下一行连接起来。

结束时会进行标准化,删除末尾空白符(如果需要空白,请用\s转义序列)并把Windows的行结束符\r\n改为简单的换行符\n,去除所有行的公共缩进,不考虑空行(因此结尾”””前的空白很重要,请缩进到要去除的空白符的末尾)。

输入与输出

读取输入

标准输入流System.in构造一个Scanner对象,随后使用nextInt等函数读取输入。

但这种输入对所有人可见,不适于从控制台读取密码,这种情况可以使用Console类来实现。不过Console类不如Scanner方便,一次必须读入一行,且不提供读取单个单词、数字的方法。

1
2
3
4
5
6
Scanner in = new Scanner(System.in);
String name = in.nextLine();

Console cons = System.console();
String userName = cons.readLine("User name: ");
char[] passwd = cons.readPassword("Password: "); // 安全起见,把密码储存在字符数组而非字符串中,处理完后应当立刻覆盖掉这个数组
static Console console()

如果有可能进行交互操作,就通过控制台窗口为交互的用户返回一个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方法创建格式化的字符串而不输出。

More

可以用s转换字符格式化任意对象,如果实现了Formattable接口,会调用formatTo方法,否则调用toString方法。

格式化规则基于本地化环境,比如,在德国,分组分隔符是点号而非逗号。后续会介绍如何控制应用的国际化行为。

文件输入与输出

读取一个文件需要Scanner对象,写入一个文件需要PrintWriter对象,随后像System.in/out一样使用即可。

1
2
Scanner in = new Scanner(Path.of("myfile.txt"), StandardCharsets.UTF_8);
PrintWriter out = new PrintWriter("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
2
3
4
5
6
int[] a; // better
int b[];
int[] c = { 2, 3, 4 };
int[] d = new int[] { 3, 4, 5 };
int[] e = new int[5]; // 会初始化为默认值 0/false/null
int f = c[10]; // Error! Java中会进行下标越界检查,越界会抛出异常

对象变量只是一个引用,如果要拷贝一份新的,需要调用Arrays.copyOf方法

增强的for循环

1
for (variable : collection) statement

collection表达式的结果需要是数组或实现了Iterable接口的类对象

命令行参数

Java程序的main方法中,程序名并不储存在args数组中,与c++不同!

排序

对数值型数组进行排序,直接调用Arrays.sort方法即可,使用的是优化的快排算法。

非数值型,需要提供比较器,参考后续部分

多维数组

实际上,Java没有多维数组,只有数组的数组,因此,在内存中不一定是连续的,也可以轻松构造不规则数组。

1
2
3
4
5
6
7
8
9
double[][] b = new double[10][6]; // java
不同于
double b[10][6]; // c++
double (*b)[6] = new double[10][6]; // c++
而是
double** b = new double*[10]; // c++
for (i = 0; i < 10; i++) {
b[i] = new double[6];
}