Java学习笔记(一):Java概述及程序设计起步
声明:本篇笔记部分摘自《Java核心技术(卷Ⅰ) - 机械工业出版社》及Java教程-廖雪峰-2025-06-16,遵循CC BY 4.0协议。这本书以及这个系列的笔记会将Java与一些编程语言的特性作比较;建议拥有一定的C/C++或其他编程语言的学习基础后,再阅读这本书或这个系列的笔记来学习Java。如果是第一次接触编程语言,建议从相对简单的C语言或Python开始;或者参考一些体系化的视频课程来进行学习,否则会感到有一定的压力。
存在由AI生成的小部分内容,仅供参考,请仔细甄别可能存在的错误。
一、Java概述
1.Java发展史
Java最早是由SUN公司(已被Oracle收购)的詹姆斯·高斯林(高司令,人称Java之父)在上个世纪 90年代初开发的一种编程语言,最初被命名为Oak,目标是针对小型家电设备的嵌入式应用(结果市场没啥反响)。互联网的崛起让Oak重新焕发了生机,于是SUN公司改造了Oak,由于Oak已经被注册,在1995年以Java的名称正式发布。随着互联网的高速发展,Java逐渐成为最重要的网络编程语言。
Java介于编译型语言和解释型语言之间。编译型语言如C、C++,代码是直接编译成机器码执行,但是不同的平台(x86、ARM等)CPU的指令集不同,因此,需要编译出每一种平台的对应机器码。解释型语言如Python、Ruby没有这个问题,可以由解释器直接加载源码然后运行,代价是运行效率太低。而Java是将代码编译成一种“字节码”,它类似于抽象的CPU指令,然后,针对不同平台编写虚拟机,不同平台的虚拟机负责加载字节码并执行,这样就实现了“一次编写,到处运行”的效果。当然,这是针对Java开发者而言。对于虚拟机,需要为每个平台分别开发。为了保证不同平台、不同公司开发的虚拟机都能正确执行Java字节码,SUN公司制定了一系列的Java虚拟机规范。从实践的角度看,JVM的兼容性做得非常好,低版本的Java字节码完全可以正常运行在高版本的JVM上。
随着Java的不断发展,出现了三个版本:
- Java SE: standard edition, 标准版本
- Java EE: enterprise edition, 企业版本
- Java ME: mico edition, 微服务版本
这三者是 Java EE > Java SE > Java ME
的关系,,Java SE就是标准版,包含标准的JVM和标准库,而Java EE是企业版,它在Java SE的基础上添加了大量的API和库,以便方便开发Web应用、数据库、消息服务等,Java EE的应用使用的虚拟机和Java SE完全相同。Java ME就和Java SE不同,它是一个针对嵌入式设备的“瘦身版”,Java SE的标准库无法在Java ME上使用,Java ME的虚拟机也是“瘦身版”。
2.Java学习路线
对于常规的Java软件应用开发者来说,学习Java SE、Java EE就足够了。Java ME的流行程度不高,如果没有特殊需求无需学习。下面是推荐的学习路线:
- 首先学习Java SE,掌握Java语言本身、Java核心开发技术以及Java标准库的使用;
- 继续学习Java EE,重点学习Spring框架、数据库开发、分布式架构;
- 如果打算学习大数据开发,那么Hadoop、Spark、Flink这些大数据平台就是需要学习的,他们都基于Java或Scala开发;
- 如果想要学习移动开发,那么就深入Android平台,掌握Android App开发。
3.JDK与JRE、JVM
- JDK:Java Development Kit,Java程序开发工具包
- JRE:Java Runtime Environment,Java运行时环境
- JVM:Java Virtual Machine,Java虚拟机
打个简单的比方,JDK类似于厨房里的食材和食谱(标准类库和方法),我们可以用来设计各种菜品(源代码);JRE是餐厅,让厨师有地方做菜,并且能够让顾客有地方享受美食(运行环境);JVM就是制作食物的厨师,负责将我们设计的菜品做成食物(编译执行)。
与上面例子稍有不同的是,JDK中也包含了JRE与调试器、编译器等开发工具,而JRE当然是包含JVM的了。即:我们通过JDK编写代码,在JRE中经过JVM的编译之后再运行。它们之间的关系如下图:
玩过Java版MC的同学应该都知道,在启动游戏之前会检查Java版本,这里的“Java版本”指的是JRE,即我们需要准备好在自己的电脑上运行Java的环境。
4.JSR与JCP
- JSR:Java Specification Request,Java规范要求
- JCP:Java Community Process,Java社区组织
为了保证Java语言有良好的可拓展性和可移植性,需要很高的代码规范性。JSR这一规范就规定了开发者在为Java添砖加瓦时的代码功能。JCP这一组织的任务之一就是审核和修订JSR。
5.Java的特点
下面是Java白皮书上提到的11个关键术语,描述了Java这门编程语言的一些特点:
- 简单性
- 面向对象
- 分布式
- 健壮性
- 安全性
- 体系结构和中立
- 可移植性
- 解释性
- 高性能
- 多线程
- 动态性
二、Hello World!
像学习其他的编程语言一样,从最简单的输出"Hello World!"开始:
1 |
|
仔细分析一下这段程序:通过访问修饰符publlic
定义了一个公有的类HelloWorld
,其中有一个main
方法,通过system.out.println()
打印了"Hello World!"
这么一个字符串。需要注意以下几点:
- 必须存在一个公共类与源代码的文件名相同,作为程序的入口;类似于C/C++中的main函数;并且该类中必须包含一个main方法,且该方法也必须声明为
public
。(在VS Code中修改主类或文件名的名称,文件名或主类的名称也会自动跟随修改) - 类的标准命名方式为驼峰命名法,即所有单词的首字母均大写;Java区分大小写,
main
和Main
不同。 - 类是Java应用的构建模块,所有的Java程序都必须放在类中。
- 语句结束的标志不是回车而是
;
,有必要的情况下可以使用回车编写一个多行的语句。 system.out.println()
方法会在打印之后自动换行,相比之下system.out.print()
方法会把新的内容打印在同一行中。
作为初学者,或许会对main
方法的参数String[] args
感到疑惑,这里做出解释:
当我们运行Java程序时,可以在命令行(或终端)后面附加参数,这些参数会被传递给
main
方法,如java HelloWorld arg1 arg2 arg3
,这里的agr1 arg2 arg3
会作为字符串数组String[]
传进main
方法。Java规范要求main
方法的签名必须如此,作为程序的统一入口点。即使我们不使用它,也需要声明,否则JVM无法识别main
方法。
如何运行这个程序?
或许有同学会觉得“这不是明摆着吗,点一下VS Code或者IDEA右上角的按钮就可以了啊,有啥好说的”,但实际上"点一下按钮"的背后,是IDE自动为我们执行了一些编译和启动的命令。为了让我们更好地理解Java程序编译运行的过程,深化对上面JDK与JVM的理解,探讨一下如何通过这些命令的方式来运行是很有必要的。
Java源码本质上是一个文本文件,我们需要先用 javac
把 HelloWorld.java
编译成字节码文件 HelloWorld.class
,然后,用 java
命令执行这个字节码文件:
首先进入源代码所在的目录,执行这一个命令将Java程序编译成字节码:
1 |
|
可以看到源代码所在的目录下出现了一个同名文件HelloWorld.class
,这就是编译之后的字节码文件。执行这一句命令来通过JVM运行字节码程序:
1 |
|
注意:给虚拟机传递的参数 HelloWorld
是我们定义的类名而不是文件名(不需要.class的后缀),虚拟机自动查找对应的class文件并执行。
三、注释
与C/C++类似,Java中的注释也有这两种方式:
1 |
|
四、基本数据类型
Java是一种强类型语言,即必须为每一个变量声明一个类型。在Java中,一共有8种数据类型(4整型+2浮点型+1字符型+1布尔型)。
1.整型
表示没有小数的数字,可以为负。
类型 | 大小 | 取值范围 |
---|---|---|
int | 4 bit | -2 147 483 648 ~ 2 147 483 647 |
short | 2 bit | -32 768 ~ 32 767 |
long | 8 bit | -9 223 372 036 854 775 808 ~ 9 223 372 036 854 775 807 |
byte | 1 bit | -128 ~ 127 |
在Java中,整型的取值范围与运行平台无关,这使得Java有较好的可移植性;一定程度上避免了C/C++在不同的机器上可能存在的溢出问题。
长整型的数据末尾有一个L或者l,十六进制前面有0x或0x,八进制的前缀是0(如8 -> 010),这种写法容易混淆所以使用得比较少。二进制数的前缀是0b或0B。数字很大时,可以加上下划线使其更易读,如2_147_483_647。
2.浮点型
表示有小数部分的数值,与C/C++类似有两种类型:
类型 | 大小 | 取值范围 |
---|---|---|
long | 4 bit | 约 (6~7位有效数字) |
double | 8 bit | 约 (15位有效数字) |
double表示的数值精度是float的两倍,因此也有“双精度浮点数”的说法。大多数情况下,都会使用double类型存储浮点数。在一个数后面加上d、D或者f、F,可以分别标识为单精度或双精度。
在浮点数中,有三个特殊数值表示溢出或错误:
- POSITIVE_INFINITY:正无穷大
- NEGATIVE_INFINITY:负无穷大
- NaN:非数字
例如,一个非零数除以零会得到无穷大的结果,而0除以0或者负数开偶次方根会得到NaN。需要注意的是,NaN不等于任何值,包括它自己,因此无法通过与NaN比较来判断结果不为NaN,而是应该使用Double.isNaN(x)
。
1 |
|
3.字符型
字符型(char类型)原本用于表示单个字符,但现在有些Unicode字符需要两个char值表示。char类型使用单引号'
表示,如char c = 'A';
。char类型的值可以表示为十六进制值,取值范围是\u0000 ~ \uFFFF
。
除了\u
这样的转义,还有一些常用的转义符号:
转义符号 | 名称 | 转义符号 | 名称 |
---|---|---|---|
\b | 退格 | \f | 换页 |
\t | 制表符 | \" | 双引号 |
\n | 换行 | \\ | 反斜线 |
\r | 回车 | \s | 空格 |
注意这里的\u
可能会与注释产生冲突,如:
1 |
|
上面两个注释在Java中都会报错,分别是因为\u
后面没有接十六进制,不是可识别的转义表示;\u00A
被直接解析成换行,导致后续内容识别成位定义的代码语句。想要解决这个问题其实也很简单,使用/* */
这样的注释就可以了。
UTF-16编码采用不同长度的代码表示所有Unicode码点(code point,指某个字符对应的代码值)。在基本多语言平面中,每个字符用16位表示,通常称为代码单元(codeunit);而辅助字符编码为一对连续的代码单元。采用这种编码对表示的每个值都属于基本多语言平面中未用的2048个值范围,通常称为替代区域(surrogatearea)(U+D800~U+DBFF用于第一个代码单元,U+DC00~U+DFFF用于第二个代码单元)。这样设计十分巧妙,因为我们可以很快知道一个代码单元是一个字符的编码,还是一个辅助字符的第一或第二部分。例如,𝕆是八元数集的数学符号,码点为U+1D546,编码为两个代码单元U+D835和U+DD46。(关于编码算法的具体描述见 https://tools.ietf.org/html/rfc27810 )。
在Java中,char类型描述了采用UTF-16编码的一个代码单元。强烈建议不要在程序中使用char类型,除非确实需要处理UTF-16代码单元。最好将字符串作为抽象数据类型来处理。
——《Java核心技术(原书第12版,机械工业出版社)》P32
4.布尔类型
布尔类型(boolean)有两个值:false
和true
,用于逻辑判断。整型值与布尔值之间不能转换。
因此在C/C++中常用的
while(n--)
在Java中就行不通了,因为Java中的n--
作为整型数据无法转换成布尔值。不过也有好处,至少避免了不小心写出if (n = 0)
而使得这段代码永远为false
的情况了(我本人经常不小心写错…)
5.枚举类型
在编写程序时,我们通常需要给一些成组的数据编号,如一周的七天编号为17、方向的上下左右编号为14、尺寸的小中大号编号为1~3等。这样做有两个缺点:
- 对不熟悉程序的人来说,看到突然出现的编号(magic number,魔法数字)会造成困扰,如
if (direction == 3)
,这样意义不明的表达会降低代码的可读性,在协作开发时造成一些麻烦。 - 有时编号可能会由于一些错误原因超出范围,使得程序出现意料之外的情况,难以进行管理和限制。
为了解决这些麻烦,人们提出了 枚举(enum) 这样的概念,通过给予一组编号有意义的名称来进行管理,有效缓解了代码可读性与范围限制的问题:
1 |
|
size类型的变量s只能存储上面声明的一些枚举值,或者是null
,表示这个变量没有设置任何值。
五、整型与常量
1.声明变量
变量的声明通常需要先指定类型,然后是这个变量的名称。如:
1 |
|
2. 变量的赋值
可以在变量声明后为其赋值,也可以在声明的同时直接赋予初始值,这一点与大多数编程语言相同。
1 |
|
从Java 10开始,局部变量可以使用var
声明,这样编译器能够通过初始值自动判断变量的类型,如:
1 |
|
赋值语句也是一个表达式,其值等于被赋予的数值,如int a = 3
这个表达式的值为3。
3.常量
在Java中,使用final
来声明一个常量。尽管const
作为Java的保留字(目前没有使用为关键字,但保留以后使用的可能性),但是目前仍然只能使用final
来定义常量。常量只能被赋值一次,后面无法进行修改。通常对常量采取全大写的命名方式。如:
1 |
|
如果想定义一个常量以在同一个类的多个方法中使用,可以使用关键字static final
将其设置为一个类常量。
1 |
|
类常量的定义通常位于main方法的外部。当其被声明为public时,其他类也可以访问到这个常量。
六、算数逻辑运算
1.算术运算符
与很多编程语言相同,Java也使用+ - * /
来完成四则运算,其中两个整数相除会进行整除操作,得到的是结果的整数部分;有浮点数参与除法运算时,得到的是完整的商值。整数的求余(也称为取模)运算使用%
。
需要注意的是,前面提到Java中整数被0除会产生一个除零异常;浮点数被0除会得到无穷大或者NaN的结果。
2. Math类
我们经常需要进行一些数学运算,如求幂、计算三角函数、使用圆周率pi、自然对数e等。Math类很好地完成了这些工作,我们只需调用相关的属性或方法即可:
属性/方法 | 描述 |
---|---|
Math.sqrt(x) | 求x的平方根 |
Math.pow(x, a) | 求x的a次幂 |
Math.sin(x) | 计算 x (弧度)的正弦值(sine),返回值范围 [-1, 1] 。 |
Math.cos(x) | 计算 x (弧度)的余弦值(cosine),返回值范围 [-1, 1] 。 |
Math.tan(x) | 计算 x (弧度)的正切值(tangent),返回值范围是全体实数。 |
Math.atan(x) | 计算 x 的反正切值(arctangent),返回弧度值,范围 [-π/2, π/2] 。 |
Math.atan2(y, x) | 计算 y/x 的反正切值(arctangent),返回弧度值,范围 [-π, π] ,能正确处理象限问题。 |
Math.toRadians(d) | 将角度制的d 转换成弧度制 |
Math.exp(x) | 计算 ,当 x 很大或很小时会返回 Infinity 或 -Infinity |
Math.log(x) | 计算 ,当 x <= 0 时会返回 NaN |
Math.log10(x) | 计算 ,当 x <= 0 时会返回 NaN |
Math.PI | 接近圆周率π的常量,值约为3.1415926 |
Math.E | 接近自然常数e的常量,值约为2.71828 |
如果在一段程序中需要大量使用Math类的属性或方法,可以在程序的头部添加引入Math类的声明即可,需要使用时直接写对应的属性或方法即可:
1 |
|
另外,Math类中的floorMod方法保证了余数不为负,这在一些进位处理中有很大的帮助,例如我们处理一个分钟的迭加逻辑时,会采用 int currentMin = (oldMin + addMin) % 60;
这样的写法,但是当 addMin
为负时,currentMin
也可能会减少到负值,需要额外的逻辑来规范处理,但是Java中可以这样解决:
1 |
|
这样就会保证 currentMin
的值始终在0 ~ 59
之间。
3.数据类型转换
上图示意了Java中的数据类型转换关系,其中实线箭头表示转换时无精度损失;虚线箭头则表示可能会发生精度损失。与大多数编程语言相同,将两个不同类型的数据用于算术运算时,Java会自动转换成较大数据类型,如 int + double -> double
、int + long -> long
。
需要手动转换数据类型时,可以使用强制类型转换(cast)。例如 int a = (int) 3.76
。需要注意以下两点:
- 当前数值超过目标类型的表示范围时会发生截断,即会得到一个不同的数值。例如
(byte) 300 == 44
。 - 不要试图直接将布尔类型的值转换成数值,应该使用三元表达式
b ? 1 : 0
进行转换。
Java支持二元运算符,可以使用+=
、-=
这样的符号。如果运算符右侧的数值与左值的类型不同,会发生强制类型转换,即如果对int
类型的x
执行x+=3.5
的操作,得到的结果是(int) x + 3.5
,即结果为3。
4.自增与自减运算符
与C/C++类似,Java支持 ++
和 --
这两个自增/减运算符,并且同样可以前置和后置。
1 |
|
5.逻辑运算符
Java采用了C++的做法,使用 ==
判断两边是否相等;分别使用 &&
、 ||
、 !
表示逻辑与、逻辑或和逻辑非,!=
表示不等于,>
、<
、<=
、>=
这样司空见惯的比较符号更是无需多言了。
需要注意的是,Java中同样存在逻辑短路的机制,即不会计算逻辑与在一端为 false
情况下、逻辑或在一端为 true
情况下,多余的表达式结果。例如:
1 |
|
这个判断中,如果 x
为0时,逻辑与运算的左边为0,无论右边是否为真都不会使得整个表达式为真,所以不会计算右边的表达式,也正好就避免了发生除零异常。
6.位运算符
指定两个整数进行位运算时,实现的是两个数字的按位运算(先转换成二进制表达,然后从低位开始逐位运算)。类似地,Java也是用下面几个符号来定义按位运算:
- &:按位与(and)
- |:按位或(or)
- ^:按位异或(xor)
- ~:按位非(not)
- <<:左移操作
-
:右移操作,高位以符号位补充,又称算数移位
-
:右移操作,高位以0补充,又称逻辑移位
7.条件运算符(三元表达式)
1 |
|
(有人用x == y ? x : y
描述自己作为x
的"家庭地位",感觉挺有意思的,大家可以仔细揣摩一下…)
8.运算符优先级
一般情况下,为了避免出现优先级问题,在复杂逻辑中添加括号明确定义运算顺序即可,不过要注意以下几点:
- 括号的优先级是最高的,会最先处理最内层括号的内容
+=
系列的优先级是从右向左,即a += b += c
的处理顺序是a += (b += c)
,先处理b += c
,即b = b + c
,然后把现在b
完成加法操作之后的值加到a
上。除此之外,+=
这个系列的符号也是从右往左处理的。&&
的优先级高于||
。
参考资料
- 廖雪峰的官方网站.Java教程[EB/OL].(2025-06-07)[2025-08-20]. https://www.cnblogs.com/echolun/p/12709761.html ↩