关于 Java
- Java 既是编译型的,又是解释性的。原始 Java 代码经过编译之后转换为一种中间字节码,Java 虚拟机(JVM)可以将字节码解释执行。
- 目前的 Java 版本有三种:Java SE、Java EE 和 Java ME。
- Java SE 是 Java 的标准版,主要用于桌面应用程序的开发,同时也是 Java 的基础,它包含 Java 语言基础、JDBC 操作、I/O、网络通信、多线程等技术。
- Java EE 是 Java 的企业版,主要用于开发企业级分布式网络程序,其核心为 EJB(企业 Java 组件模型)。
- Java ME 主要应用于移动应用开发及嵌入式开发。
- Java 是纯面向对象语言,支持面向对象的三大特征:封装、继承、多态。
- 相比于 C++,Java 拥有极其丰富的类库,使用这些类库可以很容易地开发复杂的应用程序。
CLASSPATH
环境变量:当使用java 类名
来运行一个 java 程序时,JRE 会去CLASSPATH
指定的目录寻找这个 java 类,所以CLASSPATH
中要包含.
表示当前路径,除此之外,编译和运行 java 程序还需要 JDK 的 lib 目录下dt.jar
和tools.jar
文件中的 java 类,因此还需要将这两个文件添加到CLASSPATH
中。其实,在使用 jdk 1.4 及以下的 jdk 版本时,CLASSPATH
是必须要设置的,而其他版本已经不需要设置这个变量了。
Java 程序的结构
- Java 是一种纯面向对象的语言,Java 程序的基本组成单元是类,类体中又包含属性和方法。类名首字母必须为大写,这是强制性的规定,而不是约定。所以 Java 的类名一般采用大驼峰式命名法,这是约定。
- 通常情况下,一个 Java 文件的文件名可以是任意的,但有一种情况例外:如果 Java 文件中定义了一个
public
类,则该文件的文件名必须与该public
类名相同。 - 由于 Java 文件的文件名必须与
public
类的类名相同,因此,虽然一个 Java 文件中可以定义多个类,但最多只能定义一个public
类。 - 可以使用包将功能相近的 Java 类文件组织起来,包就相当于一个目录。包名约定为全小写。
- 如需某个类能被解释器直接执行,这个类必须包含一个
main
方法,main
方法是 Java 程序的入口。含有main
方法的类叫做主类。main
方法的修饰符必须是public static void
,形参必须是String[] args
,也就是说,main
方法的写法几乎是固定的。 - 除了主类之外的类不需要包含
main
方法,因为对于一个大型的 Java 应用程序而言,往往只需要一个入口,也就是只有一个类包含main
方法,而其他类都是用于被main
方法直接或间接调用的。
下面是一个最简单的 Java 程序:
1 | package main; // 包声明 |
- 第一行声明了这个 Java 文件是属于
main
包的。 - 第二行是一个导包操作,使用
import
语句可以将其他地方的类导入当前类以使用它的代码。 - 第四行定义了一个
public
类,类名是 Hello,所以这个 Java 文件的文件名也必须是 Hello。 - 第五行定义了
main
方法,这是这个 Java 程序的入口。
注释与文档
相较于 C++,Java 多了一个文档注释的概念。文档注释包含在 /** */
中,用法与多行注释 /* */
相似。但不同的是,使用 javadoc
命令可以为指定的 Java 文件或包生成 API 文档,此时文档注释会被 javadoc
读取以作为 API 文档的内容。注意:文档注释必须写在声明语句(包括类声明、变量声明或方法声明等)之前,才会被 javadoc
读取。
javadoc
命令的使用方法:
1 | $ javadoc 选项 java文件/包 |
常用选项:
-d <directory>
:指定生成的文档所存放的路径-windowtitile <text>
:设置文档显示在浏览器中时的标题。-doctitile <html>
:设置概述页面的标题,是一段 html 格式的文本(只有对处于多个包下的源文件来生成 API 文档时,才会有概述页面)
如果需要生成更详细的文档信息,例如方法的参数、返回值等,可以在源程序中使用 javadoc 标记,将需要提取的信息写在标记后面。
常用的 javadoc 标记:
标记 | 说明 |
---|---|
@author |
指定 java 程序的作者 |
@version |
指定程序的版本 |
@deprecated |
不推荐使用的方法 |
@param |
方法的参数说明信息 |
@return |
方法的返回值说明信息 |
@see |
“参见”,用于指定交叉参考的内容 |
@exception /@throws |
抛出的异常 |
注意:javadoc 默认不会提取 @author
和 @version
的内容,如果需要提取,要在使用 javadoc 命令时加上 -author
和 -version
参数,如:
1 | $ javadoc -author -version test.java |
变量与常量
变量名的命名规则与 JavaScript 相同,可以使用美元符($)和中文。
常量的定义需要加
final
关键词:final int A = 1;
基本数据类型
整数类型
数据类型 | 占用内存 |
---|---|
byte |
1 字节(8位) |
short |
2 字节(16位) |
int |
4 字节(32位) |
long |
8 字节(64位) |
在通常情况下,直接给出一个整数值默认是 int
类型,因此,有如下两种情况需要注意:
- 如果直接将一个较小的整数值(在
byte
或short
类型的数值范围内)赋值给一个byte
或short
类型的变量,Java 会把这个整数值当做byte
或short
类型来处理。 - 如果将一个较大的整数值(超出了
int
的数值范围,但未超出long
的数值范围)赋值给long
型变量,Java 不会将它当做long
类型,所以这种操作会导致语法错误。在这个整数值后面加l
或L
(推荐使用L
,因为l
容易与数字1
搞混),则这个整数不再是默认的int
型,而是long
型,此时再将它赋值给long
型变量就不会出问题了。
注意:上面的 “当做” 并不是 “隐式类型转换”。
1 | byte a = 56; // ok。56 默认是 int 型的 56,这里被 Java 当做 byte 型的 56,然后赋值给 a。也就是说,这句话里并不存在 隐式类型转换。 |
浮点类型
数据类型 | 占用内存 |
---|---|
float |
4 字节(32位) |
double |
8 字节(64位) |
- Java 中的浮点类型默认是
double
,如果希望 Java 能把一个浮点数当做float
来处理,应该在该浮点数后面加上f
或F
。所以float a = 1.2;
这种写法会报语法错误,而float a = 1.2f;
这种写法是正确的。 - Java 的浮点数遵循 IEEE 754 标准,采用二进制数据的科学计数法来表示浮点数。对于
float
型数值,第 1 位是符号位,接下来 8 位表示指数,最后 23 位表示尾数;对于double
型数值,第 1 位是符号位,接下来 11 位表示指数,最后 52 位表示尾数。 - 由于浮点数使用二进制数据的科学计数法来表示,因此可能不能精确表示一个浮点数。使用
double
类型来表示一个浮点数会比float
类型更精准,但如果要表示的浮点数的位数很多,依然会发生这种情况。如果开发者需要精确保存一个浮点数,可以考虑使用BigDecimal
类。 - Java 还提供了三个特殊的浮点数值:正无穷大、负无穷大和非数,用来表示溢出和出错。使用一个正浮点数除以 0 会得到正无穷大;使用一个负浮点数除以 0 会得到负无穷大;使用 0.0 除以 0.0 会得到非数。需要指出的是:所有的正无穷大的值都是相等的,所有负无穷大的值也都是相等的,而
NaN
不与任何数值相等,甚至和NaN
都不相等。另外,只有浮点数除以 0 才能得到正无穷大和负无穷大,而整型数除以 0 会报错。
字符类型
Java 中的字符用 char
关键词声明,这与 C++ 相同,但由于 Java 使用 16 位的 Unicode 字符集作为编码方式,所以 Java 的字符占 2 个字节,可以存储汉字。
布尔类型
用 boolean
定义布尔类型,布尔类型只有两种值:true
和 false
。一般情况下,boolean
类型的变量占 1 个字节的空间。
引用类型
除了以上四种基本数据类型外,其他类型(如数组、类、接口等)都属于引用类型。在传参时,基本数据类型是值传递,而引用类型是引用传递。
数值的表示
Java 中整数类型有 4 种表示方式:十进制、二进制、八进制、十六进制。二进制以
0B
或0b
开头;八进制以0
开头;十六进制以0X
或0x
开头。Java 中浮点数可以用科学计数法的形式表示:
5.12e2
/5.12E2
–> $5.12 \times 10 ^ 2$从 jdk1.7 开始,开发者可以在数值中使用下划线,防止数值位数过多时“看花了眼”。
1
2int vinVal = 0B1000_0000_1111_0101;
double pi = 3.14_15_926;数值在 Java 代码中以原码的形式存在,而在计算机内部以补码的形式存在。也就是说,当输入和输出一个数时它是原码的形式,当对这个数进行运算时它是补码的形式。举个例子:
1
2
3int a = 0B0000_0000_0000_0000_0000_0000_1000_0001;
int b = ~a; // 1111_1111_1111_1111_1111_1111_0111_1110
System.out.println(b); // 1000_0000_0000_0000_0000_0000_1000_0010a
首先被赋值为 0000_0000_0000_0000_0000_0000_1000_0001(129),这是原码形式,在计算机内部原码被转换为补码形式存在,因为这个数是正数,所以补码与原码相同,仍然是 0B0000_0000_0000_0000_0000_0000_1000_0001。之后对a
的补码进行取反操作,得到 1111_1111_1111_1111_1111_1111_0111_1110,也就是b
的补码形式。最后输出b
时,要将补码形式转换为原码形式,即 1000_0000_0000_0000_0000_0000_1000_0010(-130)。
类型转换
隐式类型转换
当将一个低级类型的数据赋值给高级类型时,该数据会被隐式类型转换。
转换规则:
byte
->short
->int
->long
->float
->double
,double
的等级最高。char
可以转换为以上类型中除byte
以外的其他类型。
当一个表达式中存在多种数据类型时,整个表达式的数据类型会自动提升到与表达式中最高等级操作数同样的类型:
1
2short value = 5;
value = value - 2; // 这会发生错误,因为 2 是 int 型,该表达式提升为 int 型,导致运算结果也是 int 型,将 int 型的值赋值给 value 这个 short 型的变量就会出现错误。Java 中的
boolean
类型只能有两个值:true
和false
,且无法与任何基本类型相互转换,所以,下面几个在 C++ 中能行得通的写法在 Java 中并不适用:1
2
3
4
5int a = true; // 错,因为 boolean 无法转换为整型
if (1) { // 错,因为整型无法转换为 boolean 类型,所以这里无法使用整型数作为判断条件
// some code
}当把任意基本数据类型的值与字符串做连接运算时,基本类型的值会自动转换为字符串类型。所以,如果需要将一个基本数据类型的值转换为字符串,可以把它和一个空字符串连接。
强制类型转换
Java 中强制类型转换的写法与 C++ 相同。
将浮点数强制转换为整型数时,小数位会被直接截断。
将占用字节数较少的类型转换为占用字节数较多的类型时可以正常转换,但将占用字节数较多的类型转换为占用字节数较少的类型时,超出范围的高位(二进制下)会被截断,导致数据发生变化,如:
1
2
3short i = 129;
byte b = (byte)i;
System.out.println(b); // output: -127129 的二进制形式是 0000000010000001,正数的补码与原码相同,所以转换为补码后仍然是 0000000010000001,将它转换为
byte
类型后,前八位会被截断,只剩下 10000001,当输出时,再将补码换算成原码,也就是将后七位减一并取反,得到原码 11111111,所以最后输出的结果为 -127。通常情况下,字符串不能直接转换为基本数据类型,但通过基本类型对应的包装类中的
parseXxx
方法可以将字符串转换为基本类型。
运算符
注:数值在计算机内部进行运算时,都是采用补码形式。
算数运算符、自增自减运算符、比较运算符、赋值运算符和三目运算符都与 C++ 相同。
位运算符:
一般来说,位运算符只能操作整数类型的值和变量。
Java 中的位运算符有 7 个:
位运算符 说明 &
按位与 ` ` ~
按位非
单目运算符,将操作数的每个位(包括符号位)全部取反
示例见数值的表示中的最后一点^
按位异或
当两位相同时返回 0,不同时返回 1<<
左移运算符
将操作数的二进制码整体左移指定位数,左边截断,右边空出来的以 0 填充>>
右移运算符
将操作数的二进制码整体右移指定位数,左边空出来的以 原来的符号位 填充>>>
无符号右移运算符
将操作数的二进制码整体右移指定位数,左边空出来的以 0 填充进行移位运算时还要遵循以下规则:
- 对于低于
int
类型(如byte
、short
和char
)的操作数总是先自动转换为int
型后再移位。 - 对于
int
类型的整数移位a >> b
,当b > 32
时,系统先用b
对 32 求余(因为int
类型是 32 位),得到的结果才是真正移位的位数。例如,a >> 33
和a >> 1
结果相同。 - 对于
long
类型的整数移位a >> b
,当b > 64
时,系统先用b
对 64 求余(因为long
类型是 64 位),得到的结果才是真正移位的位数。
当进行移位运算时,只要被移位的二进制码没有发生有效位的数字丢失,不难发现左移 n 位就相当于乘以 $2^n$,右移 n 位则是除以 $2^n$。
- 对于低于
扩展后的赋值运算符:
赋值运算符可以与算数运算符、位运算符结合,成为扩展后的赋值运算符。
+=
:对于x += y
,相当于x = x + y
。这样的运算符还有:
-=
、*=
、/=
、%=
、&=
、|=
、^=
、<<=
、>>=
、>>>=
。逻辑运算符:
- 逻辑运算符有:
&&
、||
、!
、&
、|
、^
。 - 逻辑运算符只能操作
boolean
型变量或常量。 &&
、||
、!
与 C++ 相同。&&
和||
具有短路效应。- Java 多了个不短路与
&
和 不短路或|
操作符。顾名思义,使用这两个运算符没有短路效应。 ^
:异或,当两个操作数不相同时返回true
,相同时返回false
。- 可以看到,
&
、|
和^
既可以是位运算符,也可以是逻辑运算符,那么如何判断它们是位运算符还是逻辑运算符呢?当它们的操作数是整型数时,它们是位运算符;当它们的操作数是boolean
型时,它们是逻辑运算符。
- 逻辑运算符有:
流程控制
Java 中的
if-else
语句、switch-case
语句、while
、do-while
用法都与 C++ 相同。for
循环语句、break
、continue
的基本用法也与 C++ 相同。从 Java7 开始,
switch-case
语句的表达式可以是一个String
类型的字符串(但不能是StringBuffer
和StringBuilder
类型)。Java 中的
for
循环多了一种写法,这种写法叫做foreach
语句,使用foreach
语句可以方便地遍历数组:1
2
3
4int array[] = { 7, 10, 1 };
for (int item : array) {
System.out.println(item);
}在 C++ 中,
break
语句无法跳出嵌套的多层循环,Java 解决了这个问题:1
2
3
4
5
6
7
8outer: // 为外层循环设置标签,标签名为 outer
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 10; j++) {
System.out.println(i * j);
if (j == 4)
break outer; // 跳出标签名为 outer 的循环(即外层循环)
}
}与
break
语句一样,continue
也支持标签功能。注意:标签必须放在循环语句之前才有作用。
数组
数组的定义与初始化
数组定义有两种方式:
1 | type[] array; |
Java 中常采用第一种写法,因为它具有更好的语义:type[]
是一种数据类型,使用它可以定义一个引用,这个引用指向一个 type
类型的数组。
数组的初始化有两种方式:
静态初始化:
初始化时由开发者显式指定每一个元素的值,由系统自动计算数组长度。
1
2int[] array = {1, 2, 3};
int[] array = new array[] {1, 2, 3};两种写法都可以。
动态初始化:
开发者只指定数组长度,由系统为数组元素分配初始值。
1
int[] array = new int[length];
数组元素的初始值:
数组类型 数组元素的初始值 整型( long
、int
、short
、byte
)0
浮点型( double
、float
)0.0
字符类型( char
)\u0000
布尔类型( boolean
)false
引用类型(数组、类、接口等) null
多类型数组
存储在数组中的元素必须具有同样的数据类型,但由于 Java 是面向对象的语言,而类与类之间可以支持继承关系,所以用父类类型定义的数组可以存放父类类型及其所有子类类型的变量,如:
1 | Object[] array = new Object[3]; // 在 Java 中,Object 类是所有类的基类,包括基本数据类型 |
多维数组
在 Java 中可以方便地定义多维数组:
1 | int[][] a = new int[3][4]; // 定义二维数组 |
虽然 Java 语法支持定义多维数组,但是在计算机内部实际上是没有多维数组的,下面以二维数组为例,阐述 Java 定义多维数组的原理:
上面已经指出,int[]
也是一种数据类型,用它定义的变量是一个引用,这个引用指向一个 int
型的数组。那么定义二维数组也是一样的,int[][]
中的 int[]
是一种引用类型,在它后面再加 []
表示:定义一个引用,这个引用指向一个 int[]
型的数组。
下图是一个二维数组在内存中的存储示意图,a
是一个 int[][]
型的引用,它指向一个 int[]
型的数组,数组中每个元素都是一个 int[]
型的引用,每个引用又指向一个 int
型的数组。

由此也可以推出二维数组静态初始化的方法:
1 | int[][] a = new int[][] {new int[] {1, 2, 3}, new int[] {1, 2, 3}}; |
这样可以得到一个两行三列的二维数组。
使用工具类 Arrays 操作数组
为了更方便地操作数组,Java 提供了 java.util.Arrays
工具类。
该类提供了很多用于操作数组的静态方法,如:复制、排序、搜索、判断是否相等、填充、转换为字符串 等。
类和对象基础
类的定义
定义类:
1 | [修饰符] class 类名 { |
- 修饰符可以省略,也可以是
public
、final
、abstract
中的一个或多个。 - 类名使用 大驼峰命名方式。
- 如果没有定义构造函数,系统会提供一个无参的默认构造函数。
定义构造函数:
1 | [修饰符] 构造函数名(形参列表) { |
- 修饰符:可以省略,也可以是
public
、protected
、private
其中之一。 - 构造函数名:必须与类名相同
- 构造函数没有返回值,不需要写返回值类型,
void
也不行。
定义成员变量:
1 | [修饰符] 类型 成员变量名 [= 默认值]; |
- 修饰符:可以省略,也可以是
public
、protected
、private
、static
、final
,其中,public
、protected
、private
只能出现其中之一,可以与static
、final
组合起来修饰成员变量。 - 成员变量名:使用 小驼峰命名方式。
定义方法:
1 | [修饰符] 返回值类型 方法名(形参列表) { |
- 修饰符:可以省略,也可以是
public
、protected
、private
、static
、final
、abstract
,其中,public
、protected
、private
只能出现其中之一,abstract
和final
只能出现其中之一,它们可以与static
组合起来修饰方法。 - 方法名:使用 小驼峰命名方式,并建议以动词开头。
static 修饰符
用
static
修饰的成员变量和方法称为 类变量(或静态变量)、类方法(或静态方法)。在 C++ 中,静态方法与类方法并不是同一个概念,而 Java 中是。
静态成员只能访问静态成员,不能直接访问普通成员,访问普通成员需要先创建一个类的实例,再通过实例访问。
用
static
修饰的成员变量和方法,需要通过类来调用,而普通成员变量和方法则通过对象来调用。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16public class Person {
public String name = "xxx"; // 成员变量
public String getName() { // 普通方法
return this.name;
}
public static void say(String sentence) { // 静态方法
System.out.println(sentence);
}
public static void main(String[] args) { // main 方法必须用 static 修饰
say("Hello"); // 静态方法中可以直接调用另一个静态方法
Person p = new Person();
System.out.println(p.getName()); // 静态方法中调用非静态方法必须先创建一个对象,再通过对象调用
}
}
对象的产生和使用
1 | Person p = new Person(); |
- 等号左边定义了一个
Person
类型的引用p
。 - 等号右边使用
new
关键字实例化一个Person
对象,实例化时会自动调用匹配的构造函数完成初始化操作。 - 将右边新创建的对象赋值给左边的引用,该引用就指向了该对象,此后可以通过引用来调用对象的成员变量和方法。
1 | p.name = "xxx"; // 访问成员变量 |
关于引用
- Java 中的引用实际上就相当于 C/C++ 中的指针,只是 Java 把这个指针封装了起来,避免开发者进行繁琐的指针操作。
- 当没有引用指向一个对象时,这个对象就会自动被 Java 的垃圾回收机制回收掉,释放其所占的内存空间。
this 关键字
Java 提供了一个
this
关键字,它总是指向 当前对象。static
修饰的方法不能使用this
关键字。在类的一个方法中访问该类里的另一个方法或变量时,要用到
this
关键字,通过this.xxx
的方式访问。大部分情况下,
this
关键字可以省略,但这实际上只是一种假象,虽然this
被省略了,但实际上这个this
仍然是存在的。如果方法里的局部变量与类的成员变量同名,默认使用的是局部变量,如果要使用成员变量,必须用
this.xxx
的方式使用,不能省略this
。1
2
3
4
5
6
7
8
9public class Person {
public String name = "xxx";
public int age = 20;
Person(String name, int age) {
this.name = name; // name 是局部变量,this.name 是成员变量
this.age = age;
}
}this
也可以作为返回值返回。
形参个数可变的方法
在 JDK1.5 之后,Java 允许定义形参个数可变的方法(在最后一个形参的数据类型后加三个点,多个参数会被当做数组传入):
1 | public class Varargs { |
可变参数本质上是一个数组参数,也就是说,下面两种方法定义的效果是一样的:
1 | public static void test(int a, String... books); |
但在调用方法时,可变参数可以有两种传参方式:
1 | test(5, 'a', 'bb', 'ccc'); |
而数组参数只能传数组:
1 | test(5, new String[] {'a', 'bb', 'ccc'}); |
类的继承
多态
Java 基础类库
Java 集合
泛型
异常处理
注解
输入输出流
多线程
网络编程
字符串
技巧
当把任何基本数据类型的值与字符串做连接运算时,基本类型的值会自动转换为字符串类型。所以,如果需要将一个基本数据类型的值转换为字符串时,可以把它和一个空字符串连接。