# Java 面向对象编程
# Java简介
- Java 是一种以面向对象为核心的多范式编程语言
- Java 是基于 JVM 虚拟机的跨平台语言,一套代码多端部署
Java运行的原理和其他编程语言不同,它基于 JVM 的虚拟机运行:
先将 Java 源代码 (.java
文件) 通过解释器 javac
生成字节码 (.class
文件),这是一种二进制文件可以在 JVM 中执行,JVM 会把字节码逐行解释为机器指令并在本地机器执行。
所以不论任何操作系统只要有 JVM 就可以运行 Java 程序,从而实现跨平台。
Java 有三个平台,分别为:
- JavaSE:是Java标准版,提供了基础语言功能和核心类库,用于开发桌面应用程序、小型服务器应用
- JavaEE:基于JavaSE,用于构建大型、分布式、高并发的企业级应用
- JavaME:Java精简版,针对嵌入式设备的开发
# Java程序基础
# Java程序基本结构
这是一个最基本的 Java 程序
public class Hello {
public static void main(String[] args) {
System.out.println("Hello, world!"); //输出 Hello, world!
}
}
因为 Java 是一门面向对象编程的语言,所以一个程序的基本单位是 class
(类),跟在后面的 Hello
是这个类的类名
- 类名必须以英文字母开头,习惯以大写字母开头,之后可以是英文字母、数字和下划线的组合
public
是访问修饰符,被public
修饰的类、方法、变量可以在任何地方访问public
类可以从不同包中导入使用,而没有public
的私有类只能在同包内访问public static void main(String[] args)
是程序入口,JVM 在启动时会寻找main
方法作为程序起点main
方法作为程序入口点,必须是public
,否则 JVM 无法调用该方法启动程序String[] args
是一个字符串数组,参数用于接收运行Java程序时传入的参数
# 注释
单行注释:
// 单行注释
多行注释:
/*
多行注释
注释内容
注释结束
*/
# 变量和数据类型
在 Java 中定义一个变量如下:
int x = 1;
该语句定义了一个整型 int
类型的变量,名称为 x
,初始值为 1
# 变量的命名规则
- 可以使用字母、数字、
_
、$
命名,但变量名不能以数字开头 - 变量名区分大小写
- 不能使用 Java 关键字作为变量名
- 建议命名时遵循驼峰命名法
# 基本数据类型
- 整数类型:
byte
、short
、int
、long
- 浮点数类型:
float
、double
- 字符类型:
char
- 布尔类型:
boolean
数据类型 | 字节数 |
---|---|
byte | 1 Byte |
short | 2 Byte |
char | 2 Byte |
int | 4 Byte |
float | 4 Byte |
double | 8 Byte |
long | 8 Byte |
# 整数
以下是不同的整数类型所能表达的最大范围:
byte
: -128 ~ 127short
: -32768 ~ 32767int
: -2147483648 ~ 2147483647long
: -9223372036854775808 ~ 9223372036854775807
为了方便看数字的位数可以用下划线这样标注,当作为整数类型输出时下划线是不会输出的,输出:2000000000
int a = 2_000_000_000;
long
类型的数据结尾需要加 L
,如果不加 L
会被当做 int
类型,这说明 int
类型的值是可以赋值给 long
类型的变量的
long b = 9000000000000000000L;
可以将变量的值用十六进制、二进制或其他进制表示,但输出时的值是按照十进制输出的
int x = 0xff0000; // 十六进制表示的16711680
int y = 0b1000000000; // 二进制表示的512
# 浮点数
对于 float
类型的数据,需要加上 f
后缀,输出时 f
不会被输出
float f1 = 3.14f;
用科学计数法表示
float f2 = 3.14e38f; // 科学计数法表示的3.14x10^38
使用 double
类型,值不需要加后缀 f
double d1 = 1.79308;
如果在 double
类型中加了 f
后缀就相当于将 float 值赋给 double 变量时,会发生自动类型提升。
这个过程中可能会出现一些潜在风险,例如
double d1 = 1.79308f;
System.out.println(d1);
执行这段代码输出结果为 1.7930799722671509
而并非是 1.79308
,这是因为 1.79308f
是一个 float
类型字面量且转换为二进制时是无限循环小数,所以需要精度截断然后再赋值给 double
类型的变量
控制输出的浮点数位数可以通过格式化输出,%.3f
表示输出到小数点后三位
double value = 1.79308;
System.out.printf("%.3f%n", value); // 输出: 1.793
# 布尔类型
布尔类型 boolean
只有 true
和 false
两个值
boolean b1 = true;
boolean b2 = false;
boolean isGreater = 5 > 3; // 计算结果为true
int age = 12;
boolean isAdult = age >= 18; // 计算结果为false
# 字符类型
char
类型使用单引号 '
,且仅有一个字符
char a = 'A';
引用类型
以上的这些数据类型 byte
、short
、int
、long
、float
、double
、char
、boolean
都是基本类型,除此以外的其他类型都是引用类型
- 基本类型直接存储值本身,引用类型指向对象的内存地址
- 基本类型在栈内存中直接分配空间存储值,引用类型把引用对象的地址存在栈内存中,实际对象在堆内存中
- 基本类型的默认值为
0
,引用类型的默认值为null
- 在基本类型中可以直接使用
==
比较值是否相等,引用类型中==
只能比较引用地址,equals()
比较对象内容
String
就是最常见的引用类型,用于表示字符串
String s = "hello";
变量 s 只存储一个地址,这个地址会指向字符串 "hello"
在内存中存放的位置
# 常量
定义变量的时候,如果加上 final
修饰符,这个变量就变成了常量,初始化后该常量就不可再次赋值
final double PI = 3.14;
提示
常量名通常全部大写
# var关键字
为了简化局部变量的声明,Java 10 引入的局部变量类型推断关键字 var
,编译器会根据赋值语句自动推断出变量的类型
使用限制
- 只能用于局部变量声明
- 必须在声明时进行初始化
- 不能用于成员变量、方法参数或返回类型
例如,这段代码类型的名称长,写起来比较麻烦
StringBuilder sb = new StringBuilder();
可以统统使用 var ,编译器会自动判断变量的类型
var sb = new StringBuilder();
# 变量的作用域
变量的作用域仅限于它声明时所在的代码块(由括号 {}
包围的代码区域)中
# 运算
# 运算优先级
在 Java 的计算表达式中,运算优先级从高到低依次是:
优先级 | 运算符 |
---|---|
1 | () |
2 | ! ~ ++ -- |
3 | * / % |
4 | + - |
5 | << >> >>> |
6 | & |
7 | | |
8 | += -= *= /= |
算数运算符
运算符 | 说明 |
---|---|
+ | 加法 |
- | 减法 |
* | 乘法 |
/ | 除法 |
% | 取模 |
++ | 自增 |
-- | 自减 |
关系运算符
运算符 | 说明 |
---|---|
== | 等于 |
!= | 不等于 |
> | 大于 |
< | 小于 |
>= | 大于等于 |
<= | 小于等于 |
逻辑运算符
运算符 | 说明 |
---|---|
&& | 逻辑与 |
|| | 逻辑或 |
! | 逻辑非 |
位运算符
运算符 | 说明 |
---|---|
& | 按位与 |
| | 按位或 |
^ | 按位异或 |
~ | 按位取反 |
<< | 左移 |
>> | 右移 |
>>> | 无符号右移 |
赋值运算符
运算符 | 说明 |
---|---|
= | 赋值 |
+= | 加法赋值 |
-= | 减法赋值 |
*= | 乘法赋值 |
/= | 除法赋值 |
%= | 取模赋值 |
# 三元运算符
condition ? value1 : value2
condition
是运算条件,如果条件为真则返回值 value1
否则返回 value2
,例如:
int max = (a > b) ? a : b;
# 类型自动提升与强制转型
类型自动提升:在表达式计算过程中,Java自动将范围较小的数据类型转换为范围较大的数据类型以避免数据丢失
byte
、short
、char
在运算时会自动提升为int
类型- 类型提升优先级顺序:
byte
→short
→int
→long
→float
→double
- 如果表达式中有
double
,整个表达式提升为double
- 如果表达式中有
float
但没有double
,整个表达式提升为float
- 如果表达式中有
long
但没有float
或double
,整个表达式提升为long
强制转型:是将一个数据类型显式转换为另一个数据类型的操作
一般计算时小范围数会自动提升为大范围的数,如果要将大范围数转换为更小范围的类型就可以使用强制类型转换
使用括号 ()
将目标类型括起来,放在要转换的变量或表达式前面
int i = 12345;
short s = (short) i; // 12345
使用强制类型转换具有风险,可能得到错误结果,例如下面的这个例子:
int
的两个高位字节直接被截掉了,仅保留了低位的两个字节,所以导致结果错误
public class Main {
public static void main(String[] args) {
int i1 = 1234567;
short s1 = (short) i1; // -10617
System.out.println(s1);
int i2 = 12345678;
short s2 = (short) i2; // 24910
System.out.println(s2);
}
}
# 浮点数运算误差
浮点数 0.1
在计算机中就无法精确表示,因为十进制的 0.1
换算成二进制是一个无限循环小数,无论使用 float
还是 double
都只能存储一个 0.1
的近似值,所以浮点数运算可能会产生误差
public class Main {
public static void main(String[] args) {
double x = 1.0 / 10;
double y = 1 - 9.0 / 10;
// 观察x和y是否相等:
System.out.println(x);
System.out.println(y);
}
}
由于浮点数存在运算误差,所以比较两个浮点数是否相等常常会出现错误的结果。可以通过判断两个浮点数之差的绝对值是否小于一个很小的数,如果小于就默认两个浮点数相等
double r = Math.abs(x - y);
if (r < 0.00001) {
// 可以认为相等
} else {
// 不相等
}
# 短路运算
&&
(逻辑与短路运算符)
当左侧操作数为 false
时,整个表达式必然为 false
,右侧操作数不会被计算
只有左侧为 true
时,才会计算右侧操作数
||
(逻辑或短路运算符)
当左侧操作数为 true
时,整个表达式必然为 true
,右侧操作数不会被计算
只有左侧为 false
时,才会计算右侧操作数
// 非短路运算示例
boolean flag = false;
// 即使flag为false,isTrue()仍会被调用
if (flag & isTrue()) {
System.out.println("执行");
}
// 短路运算示例
// isTrue()不会被调用,因为flag为false
if (flag && isTrue()) {
System.out.println("执行");
}
短路运算可以提高程序性能,使用时要注意右侧表达式在程序中的作用
# 字符串连接
在 Java 中可以使用 +
连接任意字符串和其他数据类型
// 字符串连接
public class Main {
public static void main(String[] args) {
String s1 = "Hello";
String s2 = "world";
String s = s1 + " " + s2 + "!";
System.out.println(s); // Hello world!
}
}
# 多行字符串
从 Java 13开始,字符串可以用""" """
表示多行字符串
public class Main {
public static void main(String[] args) {
String s = """
关关雎鸠,
在河之洲。
窈窕淑女,
君子好逑
""";
System.out.println(s);
}
}
输出结果:
关关雎鸠,
在河之洲。
窈窕淑女,
君子好逑
# 数组
- 数组所有元素初始化为默认值,整型都是
0
,浮点型是0.0
,布尔型是false
- 数组一旦创建,大小就不可改变
声明数组:
int[] array;
也可以
int array[];
创建数组:
array = new int[10]; // 创建长度为10的整型数组
声明并初始化:
int[] array = new int[10];
int[] array = {1, 2, 3, 4, 5}; // 直接初始化
# 输入与输出
这是一个简单的输出程序
public class Main {
public static void main(String[] args) {
int x = 100; // 定义int类型变量x,并赋予初始值100
System.out.print(x); // 打印该变量的值
}
}
与其他的程序设计语言不同,Java是通过 print
方法进行输出的,该方法属于 System
类,定义在 java.lang
包中,但我们写代码时不需要导入它,因为 java.lang
包是 Java 的默认导入包,所有类都会自动导入
# 面向对象编程基础
# 什么是面向对象?
顾名思义,面向对象编程可不是对着男/女朋友面对面编程
这里的面向和对象编程是一种编程范式(或者说是一种编程思想),它将现实世界中的事物抽象为程序中的对象,通过对象之间的交互来解决问题。
假设在 Java 里定义一个类 class
,类就像一个模板,而对象就是这个模板下的具体实例,例如每一辆车都会有自己的特征,比如品牌,颜色,速度等,这些特征就是属性,类就是由这些属性所构成的模板
// 定义一个类
class Car {
// 属性(成员变量)
String brand;
String color;
int speed;
}
如果要在程序中使用这个定义好的类,就需要为该类编写构造方法,用于创建对象时初始化对象的状态
- 构造方法的名称必须与类名相同
- 构造方法没有返回类型
- 一个类可以有多个构造方法(构造方法重载)
// 构造方法
public Car(String brand, String color) {
this.brand = brand;
this.color = color;
this.speed = 0;
}
除了用于创建对象的构造方法,我们还要给创建好的对象设计其他的活动,例如启动汽车,或者显示汽车的当前速度
// 方法(行为)
//给对象定义一个启动的行为
public void start() {
System.out.println(brand + "汽车启动了");
}
//给对象定义一个显示当前速度的行为
public void accelerate(int increment) {
speed += increment;
System.out.println("当前速度: " + speed + " km/h");
}
现在已经设置好了类 Car
,创建一个 Car
的对象,对象通过 new
关键字和构造方法来创建
Car myCar = new Car("丰田", "红色");
这段代码中 Car myCar
中的 Car
是类名,表示数据类型 myCar
是变量名,new Car("丰田", "红色")
中的 Car
是构造方法
一个类可以创建多个对象,每个对象都有自己独立的数据
// 同一个类可以创建多个不同的对象
Car car1 = new Car("丰田", "红色");
Car car2 = new Car("本田", "蓝色");
Car car3 = new Car("奔驰", "黑色");
创建好对象后就可以在该对象中使用之前设置的方法,例如在对象 car1
上使用 start()
方法
// 让对象 car1 使用 start() 方法
car1.start();
完整实例
class Car {
// 属性
String brand;
String color;
int speed;
// 构造方法
public Car(String brand, String color) {
this.brand = brand;
this.color = color;
this.speed = 0;
}
// 方法
public void start() {
System.out.println(brand + "汽车启动了");
}
public void accelerate(int increment) {
speed += increment;
System.out.println("当前速度: " + speed + " km/h");
}
}
// 使用示例
public class Main {
public static void main(String[] args) {
// 创建对象
Car myCar = new Car("丰田", "红色");
myCar.start();
myCar.accelerate(50);
}
}
# 包
包就像一个文件夹,一个包可以包含多个 .java
文件