Java复习-最后的战役
Java复习-最后的战役
程序化编程与面向对象编程(OOP)
程序化编程:
- 主要关注解决问题所需的步骤序列。
- 编程步骤按顺序执行,通常是一个固定的流程:
- 步骤1
- 步骤2
- 步骤3
- 步骤4
- 步骤5
面向对象编程(OOP):
- 强调创建和对象之间的交互。
OOP的优势:
- 模块化: 对象的源代码可以独立于其他对象的源代码进行编写和维护。一旦创建,对象可以轻松在系统内部传递。
- 信息隐藏: 通过与对象的方法交互,外界无法了解对象内部实现的细节。
- 代码重用: 如果某个对象已经存在(可能由其他开发者编写),你可以在自己的程序中使用该对象。这使得专业人员可以实现、测试和调试复杂的任务特定对象,从而在自己的代码中放心使用。
- 可插拔性和调试方便: 如果发现某个对象存在问题,可以将其从应用程序中移除,并插入一个替代对象。
JVM、JRE 和 JDK
- JVM (Java虚拟机)
- 每个平台上运行程序时都需要Java虚拟机(JVM)。
- JVM负责解释Java技术代码,加载Java类,并执行Java技术程序。
- JRE (Java运行时环境)
- Java技术程序还需要一组平台特定的标准Java类库。
- Java类库是预先编写的代码库,可以与您编写的代码结合,创建强健的应用程序。
- JVM和Java类库结合在一起称为Java运行时环境(JRE)。
- JDK (Java开发工具包)
- Java开发工具包(JDK)包括JRE、Java工具(如
javac
、java
、jdb
)以及Java API(应用程序接口)。
- Java开发工具包(JDK)包括JRE、Java工具(如
平台独立的程序
- 编译后的Java程序的结果格式是平台独立的Java字节码,而不是特定于CPU的机器码。
- 创建字节码后,它会被一个名为虚拟机(VM)的字节码解释器解释并执行。
- 虚拟机理解平台独立的字节码,并能够在特定的平台上执行它。
Java程序的结构
- 类名
- 主方法
- 语句
- 语句结束符(如分号
;
) - 保留字(如
public
,class
等) - 注释(用
//
或/* */
标注) - 代码块(由大括号
{}
包围的代码段)
这些构成了一个标准的Java程序结构。
从控制台读取输入
标识符
标识符是用来命名变量、方法、类等的名称。它们由字母、数字、下划线(_)和美元符号($)组成,但不能以数字开头。变量
变量用于存储数据。每个变量都有一个数据类型,用于确定存储在其中的数据的类型。声明变量,赋值语句
声明变量时,需要指定变量的类型并给它一个名字。赋值语句用于将数据赋给已声明的变量。例如:1
int age = 25; // 声明并初始化变量
声明和初始化一步到位
可以在声明变量时同时给它赋初值:1
double radius = 3.14; // 声明并初始化
命名常量
常量是指值在程序运行期间不可改变的变量,通常使用final
关键字来声明:1
final double PI = 3.14159;
数值数据类型和运算符
整数除法和余数运算符
- 整数除法:当两个整数相除时,结果会是整数(忽略小数部分)。
- 余数运算符(
%
):计算两个数相除后的余数。
指数运算
Java没有直接的指数运算符,但可以使用Math.pow()
方法进行指数运算。数值字面量
- 整数字面量:表示整数的常量。
- 浮点字面量:表示浮动小数的常量。
double
与float
的区别double
具有更高的精度(64位),适用于需要高精度的小数计算。float
精度较低(32位),适用于内存较为紧张的应用。
类型转换规则
Java会自动进行“类型提升”,例如将int
转换为double
,但某些情况下需要手动进行类型转换。
从控制台读取输入
创建Scanner对象
要从控制台读取输入,需要使用Scanner
类。首先,创建一个Scanner
对象:1
Scanner input = new Scanner(System.in); // 创建一个Scanner对象
这行代码声明了一个名为
input
的Scanner
类型变量,并将System.in
(代表标准输入)传给它。使用
next
方法读取输入
使用Scanner
对象的next
方法来获取用户输入的值。例如,要读取一个double
类型的值:1
2
3System.out.print("Enter a double value: ");
Scanner input = new Scanner(System.in);
double radius = input.nextDouble(); // 获取用户输入的double值在这个例子中,程序提示用户输入一个
double
类型的值,并将其存储在radius
变量中。
整数除法与取模操作
- 整数除法 (
/
)- 当除法的两个操作数均为整数时,结果为商,小数部分被截去。
- 例如:
5 / 2
的结果是2
,因为小数部分被截去。5.0 / 2
的结果是2.5
,因为其中一个操作数是浮点数。
- 当除法的两个操作数均为整数时,结果为商,小数部分被截去。
- 取模操作符 (
%
)- 取模操作符用于获取除法后的余数。
- 例如:
5 % 2
结果是1
,因为5除以2的余数是1。12 % 2
结果是0
,因为12除以2没有余数。
- 特别地,当被除数为负数时,余数也会是负数。例如:
-7 % 3
结果是-1
-12 % 3
结果是0
-26 % -8
结果是-2
26 % -8
结果是2
- 取模操作符用于获取除法后的余数。
指数运算(Exponent Operations)
- 使用
Math.pow(a, b)
方法来计算a
的b
次方。- 例如:
Math.pow(2, 3)
结果是8.0
Math.pow(4, 0.5)
结果是2.0
(即求平方根)Math.pow(2.5, 2)
结果是6.25
Math.pow(2.5, -2)
结果是0.16
- 例如:
字面值(Literals)
- 字面值是直接出现在程序中的常量值。例如:
int i = 34;
int k = 20_10;
long x = 1000000;
double d = 5.0;
boolean b = false;
- 在这些语句中,
34
、20_10
、1000000
、5.0
和false
都是字面值。
整型字面值(Integer Literals)
- 整型字面值可以赋给整型变量,只要它能够容纳这个值。如果字面值过大,超出了变量的范围,就会发生编译错误。例如:
byte b = 1000;
会导致编译错误,因为1000
不能存储在byte
类型的变量中。
- 整型字面值默认是
int
类型,它的值范围是从-2^31
(即-2147483648
)到2^31 - 1
(即2147483647
)。 - 如果要表示一个
long
类型的整数字面值,需要在数字后面加上字母L
或l
,通常使用大写字母L
,因为小写字母l
容易与数字1
混淆。例如:2147483650L
表示一个long
类型的字面值。
增强赋值操作符(Augmented Assignment Operators)
- 增强赋值操作符将常见的算术运算与赋值操作结合在一起。常见的增强赋值操作符包括:
+=
、-=
、*=
、/=
和%=
。 - 例如:
a += b;
等价于a = a + b;
,将b
的值加到a
上。a -= b;
等价于a = a - b;
,将b
的值从a
中减去。a *= b;
等价于a = a * b;
,将a
和b
相乘,并将结果赋值给a
。a /= b;
等价于a = a / b;
,将a
除以b
,并将结果赋值给a
。a %= b;
等价于a = a % b;
,将a
除以b
后的余数赋值给a
。
自增和自减操作符(Increment and Decrement Operators)
- 前置自增(
++a
):首先将a
的值增加 1,然后返回增加后的值。 - 后置自增(
a++
):首先返回a
的当前值,然后再将a
的值增加 1。 - 前置自减(
--a
):首先将a
的值减少 1,然后返回减少后的值。 - 后置自减(
a--
):首先返回a
的当前值,然后再将a
的值减少 1。
总结:
- 前置操作符会先进行运算再返回结果,而后置操作符会先返回原值再进行运算。
布尔类型与运算符(Boolean Type and Operators)
- 布尔类型(
boolean
) 用于表示逻辑值,只能取true
或false
两个值。 - 布尔类型常用于条件判断语句中,例如
if
、while
等。
条件语句
单分支
if
语句(One-way if Statement)- 语法:如果条件为
true
,则执行指定的代码块;否则不执行。
1
2
3if (condition) {
// 执行代码
}- 语法:如果条件为
双分支
if-else
语句(Two-way if Statement)- 语法:如果条件为
true
,则执行if
代码块;否则执行else
代码块。
1
2
3
4
5if (condition) {
// 执行代码
} else {
// 执行其他代码
}- 语法:如果条件为
多分支
if-else if
语句(Multiple Alternative if Statements)- 语法:判断多个条件,根据条件选择执行的代码块。
1
2
3
4
5
6
7if (condition1) {
// 执行代码
} else if (condition2) {
// 执行代码
} else {
// 执行其他代码
}多分支
if-else if-else
语句(Multi-Way if-else Statements)- 语法:使用多个条件分支,根据条件执行不同的代码块。
switch
语句(Switch Statements)- 用于判断多个不同的值,并根据匹配的值执行对应的代码块。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17switch (status) {
case 0:
// 执行单身者税计算
break;
case 1:
// 执行已婚联合报税计算
break;
case 2:
// 执行已婚分别报税计算
break;
case 3:
// 执行户主税计算
break;
default:
System.out.println("错误:无效的状态");
System.exit(1); // 退出程序
}
逻辑运算符(Logical Operators)
- 逻辑运算符用于连接多个布尔表达式,常见的运算符有:
&&
(与,AND):当两个条件都为true
时,结果为true
。||
(或,OR):当至少一个条件为true
时,结果为true
。!
(非,NOT):对布尔值取反,true
变为false
,false
变为true
。
条件表达式(Conditional Expressions)
条件表达式(也叫三元运算符)用于简化
if-else
语句的写法:1
int result = (condition) ? value1 : value2;
如果
condition
为true
,result
为value1
;否则为value2
。
浮点数相等测试的常见错误
由于浮点数计算的有限精度,浮点数之间的相等测试是不可靠的。例如:
1
2
3Double x = 1.0 - 0.1 - 0.1 - 0.1 - 0.1 - 0.1;
System.out.println(x == 0.5); // 输出 false
System.out.println(x); // 输出 0.5000000000000001由于浮点数精度的限制,结果可能会出现微小的舍入误差。为了避免这种情况,可以使用一个允许一定误差范围的比较方法(例如,使用
Math.abs(x - y) < epsilon
)。
数学函数(Mathematical Functions)
- Math 类:Java 提供了一个
Math
类,包含了许多数学函数,例如三角函数、对数函数、指数函数等,来支持常见的数学计算。
随机数方法(random Method)
Math.random()
方法返回一个介于 [0.0, 1.0) 范围内的随机double
值。常用于生成随机数。- 示例:
0 <= Math.random() < 1.0
:返回一个介于 0 和 1 之间的随机浮动值。(int)(Math.random() * 10)
:返回一个介于 0 和 9 之间的随机整数。50 + (int)(Math.random() * 50)
:返回一个介于 50 和 99 之间的随机整数。- 一般形式:
a + (int)(Math.random() * b)
返回一个介于a
和a+b-1
之间的随机整数。
- 示例:
ASCII 码与常用字符
- ASCII 码(美国标准信息交换码)是一个 8 位编码表,用来表示大小写字母、数字、标点符号和控制字符。大多数计算机都使用 ASCII 码。
- Unicode 码 包含了 ASCII 码,
'\u0000'
到'\u007F'
对应 128 个 ASCII 字符。
字符与数值类型的转换(Casting between char and Numeric Types)
字符到整数类型转换:字符类型
char
可以被直接转换为整数类型int
,并且char
会转换为其 Unicode 值。例如:
1
int i = 'a'; // 等同于 int i = (int)'a';
结果:
i = 97
,字符'a'
的 Unicode 值是 97。
整数到字符类型转换:整数可以被转换为字符
char
,转换时取该整数值对应的 Unicode 字符。例如:
1
char c = 97; // 等同于 char c = (char)97;
结果:
c = 'a'
,数字 97 对应的字符是'a'
。
进制转换:字符也可以被转换为其他数值类型,或者通过特定的数值赋给字符类型。转换时只取低 16 位数值。
例如:
1
char ch = (char)0XAB0041; // 低 16 位的十六进制码 0041 被赋值给 ch
结果:
ch
会是字符'A'
,因为十六进制0041
对应的字符是'A'
。
浮点数转换为字符类型:当浮点数(如
65.25
)转换为char
类型时,会先将其转换为整数,然后再转换为字符。例如:
1
char c = (char) 65.25; // 将 65.25 转换为 int,再转换为 char
结果:
c = 'A'
,因为65
对应字符'A'
。
字符转换为整数:字符类型可以直接转换为其 Unicode 值。
例如:
1
2int i = (int)'A'; // 字符 'A' 的 Unicode 值赋给 i
System.out.println(i); // 输出 65
总结:Java 支持字符和数值类型之间的转换,通过明确的类型转换,可以实现数值和字符之间的相互转换。在转换过程中,字符会被转为其对应的 Unicode 值,或者将数值转换为对应的字符。
String 类型
char 类型 仅表示一个字符。要表示多个字符组成的字符串,使用
String
数据类型。例如:1
String message = "Welcome to Java";
String
实际上是 Java 库中预定义的一个类,就像System
类和Scanner
类一样。它不是原始数据类型,而是一个 引用类型。引用类型指的是变量存储的是对象的内存地址,而不是对象本身。任何 Java 类都可以作为引用类型来声明变量。示例:
1
String message = "Hello Java"; // 引用类型,引用了内容为 "Hello Java" 的字符串对象
字符串对象的简单方法
- Java 中的字符串是对象,且具有多种方法,可以用于操作字符串。以下是调用字符串对象方法的一些基本示例。
字符串比较
操作符
==
只能检测两个字符串是否指向同一个对象,而不能判断它们的内容是否相同。因此,不能使用==
来判断两个字符串变量的内容是否相同。要比较两个字符串的内容,应该使用
equals()
或compareTo()
方法。equals()
方法用于比较两个字符串的内容是否相同。compareTo()
方法用于按字典顺序比较两个字符串。如果两个字符串相同,返回 0;如果当前字符串小于参数字符串,返回负数;如果当前字符串大于参数字符串,返回正数。例如:
1
2
3String s1 = "abc";
String s2 = "abg";
System.out.println(s1.compareTo(s2)); // 输出:-4这表示
s1
和s2
从第一个不同字符开始,ASCII 值的差为 -4。
字符串与数字之间的转换
字符串转换为数字
要将字符串转换为数字,可以使用
Integer.parseInt()
或Double.parseDouble()
方法。1
2int intValue = Integer.parseInt("123"); // 将字符串 "123" 转换为 int 类型
double doubleValue = Double.parseDouble("45.67"); // 将字符串 "45.67" 转换为 double 类型
数字转换为字符串
将数字转换为字符串时,可以使用字符串连接操作符
+
或者使用String.valueOf()
或Integer.toString()
方法:1
2
3String s = 23 + ""; // 通过连接操作符将数字 23 转换为字符串 "23"
String s1 = String.valueOf(s); // 使用 String.valueOf() 方法
String s2 = Integer.toString(15); // 使用 Integer.toString() 方法这几种方法都可以将数值转换为字符串。
通过这些方法,Java 提供了便捷的方式在字符串与数值之间进行转换。
实例方法和静态方法
实例方法:字符串是 Java 中的对象,这些方法只能通过特定的字符串实例来调用,因此称为实例方法。
静态方法:静态方法是指不依赖于对象实例,可以直接通过类名来调用的方法。Java 中
Math
类中的所有方法都是静态方法,它们不依赖于任何特定的对象实例。
实例方法与静态方法的比较
调用实例方法的语法:
通过引用变量来调用实例方法:
1
Reference-Variable.methodName(arguments);
示例:
1
2String message = "Hello Java";
System.out.println(message.length()); // 调用实例方法 length()
调用静态方法的语法:
直接通过类名来调用静态方法:
1
ClassName.methodName(arguments);
示例:
1
Math.pow(2, 3); // 调用静态方法 pow()
通过这种方式,实例方法需要依赖于特定的对象,而静态方法则可以直接通过类名调用,无需对象实例。
循环语句
- while 循环:当条件为真时,反复执行循环体中的语句。
- do-while 循环:首先执行一次循环体,然后检查条件是否满足,若满足则继续执行循环。
- for 循环:通常用于已知循环次数的情况,通过三个部分(初始化、条件判断、更新)控制循环。
break 语句
break
语句用于跳出整个循环,立即终止循环的执行。例如:
1 | public class TestBreak { |
在这个例子中,当 sum
大于或等于 100
时,break
会跳出 while
循环。
continue 语句
continue
语句用于跳过当前循环中的一次迭代,继续执行下一次迭代。例如:
1 | public class TestContinue { |
在这个例子中,当 number
等于 10 或 11
时,continue
会跳过当前的迭代,不执行
sum += number
语句,直接进入下一次循环。
总结:
break
:终止整个循环,跳出循环外部。continue
:跳过当前循环的迭代,直接进入下一次循环。
方法
定义方法 (Defining Methods)
方法是由方法名称、参数、返回值类型、修饰符以及方法体组成。方法的定义语法如下:
1 | 修饰符 返回值类型 方法名(参数列表) { |
例如:
1 | public static int max(int num1, int num2) { |
在这个例子中:
public
是方法的修饰符。static
是静态修饰符,表示该方法属于类本身。int
是返回值类型,表示方法返回一个整数。max
是方法名。(int num1, int num2)
是参数列表,表示方法接受两个整数作为输入。- 方法体部分包含实际的代码逻辑。
调用方法 (Calling Methods)
调用方法时,必须传入正确的实参(实际参数),并得到返回值。例如:
1 | public static void main(String[] args) { |
max(i, j)
调用max
方法,并传入i
和j
作为参数。- 方法返回值被存储在变量
k
中,并在System.out.println
中输出。
通过值传递 (Pass by Value)
Java 中的方法传递参数是通过值传递的。这意味着,传入的方法参数是实参的副本,方法内的修改不会影响到实参的值。
例如:
1 | public static int max(int num1, int num2) { |
尽管方法 max
的形参 num1
和
num2
存储了实参的值,但是它们是独立的变量,修改
num1
和 num2
不会影响调用 max
方法时传入的 i
和 j
。
方法重载 (Overloading Methods)
方法重载允许使用相同的名称定义多个方法,只要它们的参数列表不同即可。Java 编译器根据方法签名(方法名和参数列表)来决定调用哪个方法。
例如:
1 | public static int max(int num1, int num2) { |
- 这两个
max
方法具有相同的名称,但一个接受int
类型参数,另一个接受double
类型参数。 - 编译器根据传入参数的类型选择正确的重载方法。
数组
介绍数组 (Introducing Arrays)
声明数组变量 (Declaring Array Variables)
数组在 Java 中是一个存储相同类型元素的容器。声明数组变量的方式如下:1
type[] arrayName;
例如:
1
int[] numbers; // 声明一个整数数组
创建数组 (Creating Arrays)
创建数组的方式是使用new
关键字:1
arrayName = new type[size];
例如:
1
numbers = new int[5]; // 创建一个包含 5 个整数的数组
声明和创建数组 (Declaring and Creating in One Step)
也可以将数组声明和创建合并为一步:1
int[] numbers = new int[5]; // 声明并创建一个包含 5 个整数的数组
数组的长度 (The Length of an Array)
数组的长度可以通过.length
属性获取:1
int length = numbers.length; // 获取数组的长度
默认值 (Default Values)
在创建数组时,Java 会为数组元素赋予默认值:- 整型数组的默认值是 0
- 布尔型数组的默认值是
false
- 对象数组的默认值是
null
使用索引的变量 (Indexed Variables)
数组的每个元素都有一个索引,可以通过索引访问数组元素。例如:1
numbers[0] = 10; // 访问数组中的第一个元素并赋值
简写初始化数组 (Using the Shorthand Notation)
数组还可以使用简写方式进行初始化:1
int[] numbers = {1, 2, 3, 4, 5}; // 直接初始化数组
处理数组 (Processing Arrays)
初始化数组 (Initializing Arrays with Input Values)
数组可以通过输入值进行初始化,或者可以通过for
循环进行赋值。打印数组 (Printing Arrays)
可以使用for
循环来打印数组的每个元素:1
2
3for (int i = 0; i < numbers.length; i++) {
System.out.println(numbers[i]);
}求数组元素和 (Summing all Elements)
可以通过for
循环遍历数组并求出所有元素的和:1
2
3
4int sum = 0;
for (int i = 0; i < numbers.length; i++) {
sum += numbers[i];
}找出数组中最大元素 (Finding the Largest Element)
可以遍历数组,找出最大元素:1
2
3
4
5
6int max = numbers[0];
for (int i = 1; i < numbers.length; i++) {
if (numbers[i] > max) {
max = numbers[i];
}
}复制数组 (Copying Arrays)
可以使用Arrays.copyOf
或System.arraycopy
方法来复制数组。将数组传递给方法 (Passing Arrays to Methods)
数组可以作为参数传递给方法。例如:1
2
3
4
5public static void printArray(int[] arr) {
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}从方法返回数组 (Returning an Array from a Method)
方法可以返回数组。例如:1
2
3public static int[] createArray() {
return new int[]{1, 2, 3, 4, 5};
}
Arrays.sort 方法
Java 提供了 Arrays.sort
方法来对数组进行排序。Arrays.sort
是在
java.util.Arrays
类中定义的,它有多个重载方法,可以对
int
、double
、char
、short
、long
和 float
类型的数组进行排序。例如:
1 | double[] numbers = {6.0, 4.4, 1.9, 2.9, 3.4, 3.5}; |
排序后的结果将是:
numbers
:{1.9, 2.9, 3.4, 3.5, 4.4, 6.0}chars
:{'A', '4', 'f'}
Main 方法就是普通方法 (Main Method Is Just a Regular Method)
Java 中的 main
方法可以像普通方法一样被调用。例如:
1 | public class A { |
在这个例子中,A
类的 main
方法调用了
B
类的 main
方法,传递了一个字符串数组。
命令行参数 (Command-Line Parameters)
main
方法的参数 String[] args
可以接收从命令行传递的参数。例如,可以通过命令行启动程序并传递参数:
1 | java TestMain arg0 arg1 arg2 |
在 main
方法中,可以通过
args[0]
、args[1]
等访问命令行参数。
类与对象
定义类和创建对象 (Defining Classes and Creating Objects)
构造器 (Constructors) 和 默认构造器 (Default Constructor)
构造器是一个特殊的方法,它在创建对象时被调用,用于初始化对象的状态。如果没有定义构造器,Java 会提供一个默认构造器,它会初始化对象为默认值。声明对象引用变量 (Declaring Object Reference Variables)
在 Java 中,声明一个对象引用变量的语法如下:1
ClassName objectName;
例如:
1
Circle circle; // 声明一个 Circle 类型的对象引用
访问对象的成员 (Accessing Object’s Members)
通过对象引用变量访问对象的属性和方法:1
2objectName.property; // 访问对象的属性
objectName.method(); // 调用对象的方法引用数据域 (Reference Data Fields)
类中的数据域可以是实例变量或静态变量,实例变量是与对象实例关联的,而静态变量是与类关联的,所有对象共享同一个静态变量。数据域的默认值 (Default Value for a Data Field)
对于类中的数据字段,Java 会为其提供默认值:- 整型变量的默认值是
0
- 布尔型变量的默认值是
false
- 对象引用的默认值是
null
- 整型变量的默认值是
随机类 (The Random Class)
java.util.Random
类提供了更强大的随机数生成功能。可以使用nextInt()
方法生成随机的整数:1
2Random rand = new Random();
int randomNum = rand.nextInt(100); // 生成 0 到 99 之间的随机整数
静态变量、常量和方法 (Static Variables, Constants, and Methods)
静态变量 (Static Variables)
静态变量是类级别的,所有实例共享同一个静态变量。静态变量通过static
关键字声明:1
static int count; // 静态变量
静态方法 (Static Methods)
静态方法属于类而不是对象,不能访问实例变量,只能访问静态变量。所有的静态方法都可以通过类名调用:1
Math.max(5, 10); // 静态方法
常量 (Constants)
常量是值不可改变的变量,通常与final
和static
关键字一起使用:1
final static double PI = 3.14159; // 定义常量
实例方法和静态方法的区别
- 实例方法可以访问实例数据域和静态数据域,并且可以调用实例方法和静态方法。
- 静态方法只能访问静态数据域,不能访问实例数据域或调用实例方法。
将对象传递给方法 (Passing Objects to Methods)
在方法中传递对象时,传递的是对象的引用,而不是对象本身。通过引用可以访问对象的成员。
对象数组 (Array of Objects)
对象数组是由引用变量组成的数组,因此访问数组元素时需要两层引用。以下是创建对象数组的示例:
1 | Circle[] circleArray = new Circle[10]; // 创建一个包含 10 个 Circle 对象的数组 |
可以通过以下方式访问数组中的元素:
1 | circleArray[1].getArea(); // 访问数组中第二个 Circle 对象的面积 |
不可变对象和类 (Immutable Objects and Classes)
如果一个对象的内容在创建后不能改变,则该对象是不可变的,对应的类称为不可变类。例如,String
类就是不可变的。
要创建一个不可变类,必须满足以下条件:
- 所有数据字段都应该是
private
。 - 不提供修改数据字段的 setter 方法,也不提供能返回可修改数据字段引用的 getter 方法。
this 关键字 (The this Keyword)
this
关键字是指当前对象的引用。它常用于以下几种情况:
引用类的隐藏数据字段:当方法的参数与数据字段名称相同时,可以使用
this
来区分它们。调用当前类的其他构造器:通过
this()
调用同一个类中的其他构造器:1
2
3
4
5
6
7
8
9
10
11public class Circle {
private double radius;
public Circle() {
this(1.0); // 调用另一个构造器
}
public Circle(double radius) {
this.radius = radius;
}
}
包装类
包装类 (Wrapper Classes)
包装类 (Wrapper Classes)
在 Java 中,基本数据类型如int
、double
、char
等不能直接作为对象传递到方法中,包装类提供了将这些基本类型包装成对象的功能。Java 为每种基本数据类型提供了对应的包装类:Boolean
(包装布尔值)Character
(包装字符)Short
(包装短整型)Byte
(包装字节型)Integer
(包装整型)Long
(包装长整型)Float
(包装单精度浮点型)Double
(包装双精度浮点型)
使用包装类的一个重要优势是可以将基本数据类型作为对象处理,方便进行方法传递和其他操作。
静态 valueOf 方法
valueOf(String s)
方法可以根据字符串值创建包装类的对象。例如:1
2Double doubleObject = Double.valueOf("12.4");
Integer integerObject = Integer.valueOf("12");基本类型和包装类类型之间的自动转换
Java 支持基本数据类型和包装类之间的自动转换(自动装箱和自动拆箱)。例如,基本类型的值可以自动装箱成包装类对象,而包装类对象可以自动拆箱成基本类型值:1
2Integer[] intArray = {1, 2, 3}; // 自动装箱
System.out.println(intArray[0] + intArray[1] + intArray[2]); // 自动拆箱并相加
String 类
构造字符串 (Constructing a String)
创建字符串的方法有多种,如直接使用双引号创建字符串,或者使用String
类的构造方法创建。获取字符串长度和检索单个字符 (Obtaining String Length and Retrieving Individual Characters)
- 使用
length()
方法获取字符串的长度。 - 使用
charAt(int index)
方法获取字符串中指定位置的字符。
- 使用
字符串拼接 (String Concatenation)
使用concat()
方法拼接字符串。也可以使用+
运算符直接拼接。
StringBuilder 类和 StringBuffer 类
StringBuilder/StringBuffer 类的介绍
StringBuilder
和StringBuffer
类是String
类的替代方案。这两个类的主要优点是能够修改字符串的内容,而String
对象一旦创建,其值是不可改变的。StringBuilder
和StringBuffer
比String
更加灵活,可以在字符串中插入、追加或删除内容。修改字符串 (Modifying Strings in StringBuilder)
StringBuilder
提供了多种方法来修改字符串:append(data: char[])
: 将字符数组添加到字符串末尾。insert(index: int, data: char[])
: 在指定位置插入字符数组。delete(startIndex: int, endIndex: int)
: 删除指定范围的字符。reverse()
: 将字符串反转。setCharAt(index: int, ch: char)
: 设置指定位置的字符。
示例
1
2
3StringBuilder stringBuilder = new StringBuilder("Welcome to");
stringBuilder.append(" Java"); // 追加 "Java"
System.out.println(stringBuilder.toString()); // 输出 "Welcome to Java"
通过 StringBuilder
或
StringBuffer
,可以动态修改字符串内容,适用于需要频繁修改字符串的场景。
继承与多态
使用super关键字
调用父类构造方法:
- 子类无法继承父类的构造方法,只能使用
super
关键字在子类构造方法中调用父类的构造方法。 - 调用父类构造方法的语法是:
super()
或super(arguments)
。super()
:调用父类的无参构造方法。super(arguments)
:调用父类的带参数构造方法。
- 子类无法继承父类的构造方法,只能使用
super的使用规则:
super
语句必须位于子类构造方法的第一行,这是调用父类构造方法的唯一方式。例如:
1
2
3
4public CircleFromSimpleGeometricObject(double radius, String color, boolean filled) {
super(color, filled); // 调用父类构造方法
this.radius = radius;
}在Java中,父类构造方法总是被调用的。如果子类没有显式调用父类构造方法,编译器会自动插入
super()
。
构造方法链(Constructor Chaining)
构造一个类的实例时,会沿着继承链依次调用所有父类的构造方法,直到最终调用到
Object
类的构造方法。这一过程称为构造方法链(Constructor Chaining)。例如,子类构造方法会首先调用父类构造方法,如果父类继承自其他类,那么父类的构造方法也会调用其父类的构造方法,依此类推。
例子:
1
2
3
4public A(double d) {
super(); // 自动调用父类构造方法
// 其他代码
}
方法重写(Overriding)
重写方法:子类继承了父类的方法,有时需要修改父类方法的实现,这就是方法重写(Overriding)。
例如:
1
2
3
4
5
6public class Circle extends GeometricObject {
// 重写父类的toString方法
public String toString() {
return super.toString() + "\nradius is " + radius;
}
}在上面的例子中,
Circle
类重写了GeometricObject
类的toString
方法。
方法重载(Overloading)与方法重写(Overriding)的区别
方法重载:在同一个类或通过继承关系的类中,定义多个同名但参数类型、个数或顺序不同的方法。
方法重写:在子类中重新实现父类的方法,方法签名必须相同,目的是改变父类方法的行为。
示例:
重写(Overriding):
1
2
3
4
5class A extends B {
public void p(double i) {
System.out.println(i);
}
}重载(Overloading):
1
2
3
4
5class A extends B {
public void p(int i) {
System.out.println(i);
}
}
重载的特点是方法名相同,参数不同;而重写则是在子类中提供新的方法实现,覆盖父类的方法。
Object类及其方法
所有类都继承自
java.lang.Object
类:- 每个Java类都继承自
Object
类,除非明确指定继承其他类。如果没有显式指定父类,Java会默认使用Object
类作为父类。 - 例如,
public class Circle { ... }
等效于public class Circle extends Object { ... }
。
- 每个Java类都继承自
toString()方法:
toString()
方法用于返回对象的字符串表示。Object
类的默认实现返回的是包含类名、@符号和对象的哈希码的字符串(例如Loan@15037e5
)。通常,建议重写
toString()
方法,以返回更具可读性和信息性的字符串表示。例如:
1
2Loan loan = new Loan();
System.out.println(loan.toString()); // 输出如Loan@15037e5
多态(Polymorphism)
多态的定义:
- 多态意味着超类类型的变量可以引用子类对象。
- 例如,
Circle
是GeometricObject
的子类,而GeometricObject
是Circle
的超类。 - 方法
m(Object x)
可以接受任何类型的对象,这就体现了多态。
动态绑定:
- 当调用
toString()
方法时,Java虚拟机(JVM)根据对象的实际类型(例如GraduateStudent
、Student
、Person
或Object
)动态决定使用哪个类的toString()
方法。 - 这就是动态绑定,在运行时根据对象的实际类型选择方法的实现。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23public class PolymorphismDemo {
public static void main(String[] args) {
m(new GraduateStudent());
m(new Student());
m(new Person());
m(new Object());
}
public static void m(Object x) {
System.out.println(x.toString());
}
}
class GraduateStudent extends Student {}
class Student extends Person {
public String toString() {
return "Student";
}
}
class Person extends Object {
public String toString() {
return "Person";
}
}- 当调用
动态绑定
- 动态绑定意味着方法的调用在运行时决定,JVM根据对象的实际类型来选择合适的方法实现。
- 假设一个对象
o
是类C1
的实例,同时也是C2
、C3
...等类的实例。在调用方法时,JVM会按照继承链(从C1
到C2
等)查找并执行相应的实现。
对象的转换(Casting)
隐式转换:
- 如果一个子类对象被赋值给父类类型的变量,Java会自动进行类型转换。
- 例如:
Object o = new Student();
,这是一种隐式转换,因为Student
是Object
的子类。
显式转换:
当将父类对象转换为子类对象时,必须使用显式转换。例如:
1
2Fruit fruit = new Apple();
Apple apple = (Apple) fruit; // 显式转换
instanceof操作符:
使用
instanceof
操作符可以测试一个对象是否是某个类的实例。例如:
1
2
3if (o instanceof Circle) {
System.out.println("The circle diameter is " + ((Circle) o).getDiameter());
}
ArrayList类
ArrayList
类可以存储任意数量的对象,并且大小是动态可变的。ArrayList
的常用方法:add(o: E)
:添加元素到列表末尾。add(index: int, o: E)
:在指定位置插入元素。clear()
:移除所有元素。contains(o: Object)
:检查列表是否包含指定元素。get(index: int)
:获取指定位置的元素。remove(o: Object)
:移除指定的元素。size()
:返回列表中元素的个数。
Array与ArrayList的区别:
操作 数组 ArrayList 创建 String[] a = new String[10];
ArrayList<String> list = new ArrayList<>();
访问元素 a[index]
list.get(index);
更新元素 a[index] = "London";
list.set(index, "London");
获取大小 a.length
list.size();
添加元素 不适用 list.add("London");
插入元素 不适用 list.add(index, "London");
删除元素 list.remove(index);
list.remove(Object);
清空元素 不适用 list.clear();
可访问性修饰符(Accessibility Modifiers)
- Java中有四种访问修饰符:
public
、protected
、default
(包内访问)和private
。 - 各修饰符的访问范围:
public
:可以在任何地方访问。protected
:可以在同一个包内访问,也可以在不同包的子类中访问。default
:只有同一个包内的类可以访问。private
:只有当前类中的方法和成员可以访问。
异常
异常处理概述
抛出异常
- Java中,异常分为两类:必检异常(Checked Exceptions)和免检异常(Unchecked Exceptions)。
RuntimeException
、Error
及其子类属于免检异常,不需要强制处理。而其他异常则是必检异常,编译器会强制程序员检查并通过try-catch
块处理,或者在方法声明中使用throws
关键字。
声明异常
每个方法必须声明它可能抛出的必检异常,这就是声明异常。
例如:
1
2public void myMethod() throws IOException
public void myMethod() throws IOException, OtherException1, …, OtherExceptionN
异常处理示例
- 使用
try-throw-catch
块来处理异常。如果出现异常,会捕获并处理它。
- 使用
重新抛出异常
如果异常处理器无法处理异常,或者希望将异常交给调用者处理,Java允许在
catch
块中重新抛出异常。示例:
1
2
3
4
5
6try {
statements;
} catch (TheException ex) {
perform operations before exits;
throw ex;
}throw ex
会将异常重新抛出,交由调用者的异常处理器进行处理。
finally 子句
无论是否发生异常,
finally
子句中的代码都会被执行。示例:
1
2
3
4
5
6
7try {
statements;
} catch (TheException ex) {
handling ex;
} finally {
finalStatements;
}
捕获异常
异常在
try-catch
块中捕获并处理。每种异常可以有不同的处理器。示例:
1
2
3
4
5
6
7
8
9
10try {
statements; // 可能抛出异常的语句
} catch (Exception1 exVar1) {
handler for exception1;
} catch (Exception2 exVar2) {
handler for exception2;
} ...
catch (ExceptionN exVar3) {
handler for exceptionN;
}
自定义异常类
- 如果需要传递更详细的异常信息,可以定义自己的异常类。例如,当半径为负值时,可以抛出一个自定义的异常。
文本I/O
File
类封装了文件或路径的属性,但不包含读写数据的方法。- 若要执行I/O操作,需要使用适当的 Java I/O 类创建对象,使用这些对象提供的方法来读取或写入数据。
- 本节介绍了如何使用
Scanner
类读取数据,使用PrintWriter
类写入数据。
使用 PrintWriter
写入数据
PrintWriter
类用于向文件写入数据,支持不同类型的数据输出:
PrintWriter
构造方法PrintWriter(filename: String)
:创建一个用于写入指定文件的PrintWriter
对象。
- 常用方法
print(s: String)
:写入字符串。print(c: char)
:写入字符。print(cArray: char[])
:写入字符数组。print(i: int)
:写入整数。print(l: long)
:写入长整数。print(f: float)
:写入浮点数。print(d: double)
:写入双精度浮点数。print(b: boolean)
:写入布尔值。
println
方法println
方法类似于print
方法,但在输出后会添加一个行分隔符。行分隔符由系统定义:- Windows:
\r\n
- Unix/Linux:
\n
- Windows:
printf
方法printf
方法用于格式化输出,已在第3.6节“格式化控制台输出和字符串”中介绍。
自动关闭资源的 try-with-resources
语法
在 JDK 7 中,引入了 try-with-resources
语法,帮助程序员自动关闭资源(如文件),避免忘记关闭文件导致资源泄露。
语法
1
2
3try (declare and create resources) {
// 使用资源处理文件
}try-with-resources
语法会自动关闭在try
块中声明和创建的资源,无需显式调用close()
方法。这减少了资源管理的复杂性,并确保资源最终会被关闭。
示例: 使用 PrintWriter
写入数据并自动关闭文件:
1 | try (PrintWriter writer = new PrintWriter("output.txt")) { |
在这个例子中,PrintWriter
对象会在 try
块结束时自动关闭,无需显式调用 close()
。
抽象类与抽象方法
抽象类与抽象方法
抽象类与接口的对比
- 接口(Interface) 支持多重继承,而 抽象类(Abstract Class) 只支持单继承。
接口与抽象类的区别
- 在接口中,数据必须是常量;而在抽象类中,可以有各种类型的数据。
- 接口中的每个方法只有方法签名,没有实现;而抽象类可以有具体的方法实现。
实例与类型
- 假设
c
是Class2
的实例,c
也是Object
、Class1
、Interface1
、Interface1_1
、Interface1_2
、Interface2_1
和Interface2_2
的实例。 - 所有类共享一个共同的根类
Object
,但是接口没有单一的根接口。接口定义了一个类型,接口类型的变量可以引用任何实现了该接口的类的实例。
- 假设
定义接口
Java使用以下语法来定义接口:
1
2
3
4public interface InterfaceName {
constant declarations;
abstract method signatures;
}一个接口可以继承多个接口;例如:
interface a extends cls1, cls2;
。一个类可以实现多个接口;例如:
class b implements face1, face2;
。但是,一个类只能继承一个类,这是Java的继承特点。
Comparable接口
Comparable
接口定义了compareTo
方法,用于比较对象的大小。该接口位于
java.lang
包中:1
2
3public interface Comparable<E> {
public int compareTo(E o);
}Comparable
是一个泛型接口,接口中的泛型类型E
在实现时被替换为具体的类型。compareTo
方法用于判断当前对象相对于给定对象o
的顺序。当当前对象小于、等于或大于给定对象时,分别返回负整数、零或正整数。
使用
Comparable
接口进行排序- 由于所有实现了
Comparable
接口的对象都有compareTo
方法,因此,Java API 中的java.util.Arrays.sort(Object[])
方法可以使用compareTo
方法对数组中的对象进行比较和排序。 java.util.Arrays.sort(array)
方法要求数组中的元素是Comparable<E>
的实例。
- 由于所有实现了
ComparableRectangle
类- 在
ComparableRectangle
类中,使用implements
关键字表示该类实现了Comparable
接口,并实现了接口中的compareTo
方法。 - 由于
ComparableRectangle
对象是Comparable
接口的实例,Java API 中的Arrays.sort(Object[])
方法可以通过compareTo
方法对数组中的ComparableRectangle
对象进行比较和排序。
- 在
Cloneable 接口
Cloneable 接口
Cloneable
接口是一个空接口,位于java.lang
包中:1
2
3package java.lang;
public interface Cloneable {
}Cloneable
接口用于标记一个类是可克隆的。它本身没有任何方法,但它的作用是表示一个类具有“可克隆”特征。
实现 Cloneable 接口
实现
Cloneable
接口的类,其对象可以使用Object
类中定义的clone()
方法进行克隆。Object
类的clone()
方法定义如下:1
protected native Object clone() throws CloneNotSupportedException;
关键字
native
表示该方法在本地平台(JVM)中实现,不是用 Java 编写的。protected
关键字限制了该方法只能在同一包内或子类中访问。通常需要重写clone()
方法并将其可见性修改为public
,以便在任何包中使用。
克隆的条件
- 一个对象能够被克隆,通常需要满足两个条件:
- 实现
Cloneable
接口,确保克隆合法,避免抛出CloneNotSupportedException
。 - 重写
clone()
方法。
- 实现
- 一个对象能够被克隆,通常需要满足两个条件:
浅拷贝 vs. 深拷贝
浅拷贝(Shallow Copy)
浅拷贝是指仅复制对象本身,包括对象中的基本变量,但不复制对象引用的其他对象。
Object
类的clone()
方法完成的是浅拷贝。浅拷贝的例子:
1
2House house1 = new House(1, 1750.50);
House house2 = (House) house1.clone();house1 == house2
为假,因为它们是不同的对象。house1.whenBuilt == house2.whenBuilt
为真,因为它们引用同一个Date
对象。
深拷贝(Deep Copy)
深拷贝不仅复制对象本身,还复制对象中引用的所有对象。深拷贝需要手动实现。
深拷贝的例子:
1
2House house1 = new House(1, 1750.50);
House house2 = (House) house1.clone();house1 == house2
为假。house1.whenBuilt == house2.whenBuilt
为假,因为它们引用不同的Date
对象(深拷贝时,whenBuilt
也被克隆了)。
实现深拷贝
若要进行深拷贝,需要在
clone()
方法中手动克隆引用类型的成员。例如:1
2
3House houseClone = (House) super.clone();
houseClone.whenBuilt = (java.util.Date) (whenBuilt.clone());
return houseClone;这样就确保了
house1
和house2
拥有不同的Date
对象引用,从而实现了深拷贝。
JavaFx
JavaFX 基本结构
- JavaFX 应用程序的基本结构
- 每个 JavaFX 应用程序都定义在一个继承自
javafx.application.Application
类的类中。 - 必须重写
start(Stage)
方法,该方法用于设置应用的界面。 - JavaFX 应用的核心组成元素是 Stage(舞台)、Scene(场景) 和 Nodes(结点)。
- 每个 JavaFX 应用程序都定义在一个继承自
- Stage 和 Scene
- Stage(舞台):舞台是一个窗口,负责显示应用的场景。当应用启动时,JVM
会自动创建一个主舞台对象(
primaryStage
),并将其传递给start
方法。 - Scene(场景):场景是舞台上的内容区域,它包含了多个结点(如按钮、文本框、标签等)。创建场景时,可以通过
Scene(node, width, height)
构造方法指定场景的大小并添加结点。 - 结点(Nodes):结点是构成场景的基本元素,包括面板、控件、图形等,像
Button
(按钮)就是一个结点。
- Stage(舞台):舞台是一个窗口,负责显示应用的场景。当应用启动时,JVM
会自动创建一个主舞台对象(
- launch 方法
launch
是Application
类中的一个静态方法,用于启动独立的 JavaFX 应用程序。- 每个 JavaFX 程序必须继承
Application
类,并重写start
方法。
- 创建一个简单的 JavaFX 应用
- 在
start
方法中,我们通常会创建一个界面元素(如按钮),并将其放入一个Scene
中,然后将该场景设置到Stage
(主窗口)上进行显示。 - 示例代码流程:
- 重写
start
方法,设置 UI 组件并添加到场景。 - 创建按钮
Button
对象,并将其放入Scene
中。 - 设置
Stage
的标题。 - 显示
Stage
,使应用程序可见。
- 重写
- 在
- VBox 布局
- VBox:是一个布局容器,它会将子结点垂直排列在一个列中。
- 这种布局方式适用于需要垂直堆叠组件的场景。与 HBox 不同,HBox 会将子结点水平排列。
VBox
是一种常用的布局管理器,可以用于创建简单的垂直布局。
- FlowPane、HBox 和 VBox
FlowPane
:可以将子结点布局成多行或多列。HBox
和VBox
:分别用于将子结点水平或垂直排列,适用于需要单行或单列布局的场景。
- 实例分析
- 在代码中:
getHBox()
方法返回一个包含两个按钮和一个图像视图的HBox
布局容器。getVBox()
方法返回一个包含五个标签的VBox
布局容器。setMargin
方法用于设置结点之间的外边距,使得布局更为美观。
- 在代码中:
## Java事件委托类型
Java 事件委派模型
- 事件委派模型
- Java 采用基于 委派模型(the delegation
model)来处理事件:
- 事件源(Event Source):触发事件的对象。
- 事件处理器(Event Handler / Listener):处理该事件的对象,也叫事件监听器。
- 事件源触发事件后,事件处理器接收并处理事件。
- 例如,在 JavaFX 中,事件的处理是通过
EventHandler<T>
接口来实现的,该接口定义了handle(T e)
方法用于处理事件。
- Java 采用基于 委派模型(the delegation
model)来处理事件:
- 事件处理流程
- 事件处理器必须满足以下两个条件:
- 监听器对象必须是对应事件处理接口的实例。JavaFX
提供了一个统一的事件处理器接口
EventHandler<T extends Event>
。 - 通过调用
source.setOnXEventType(listener)
方法进行事件监听器的注册。
- 监听器对象必须是对应事件处理接口的实例。JavaFX
提供了一个统一的事件处理器接口
- 事件处理器必须满足以下两个条件:
内部类(Inner Classes)
- 定义
- 内部类(Inner Class)是定义在另一个类中的类。
- 优势:在某些应用中,使用内部类可以使程序更加简洁和易于理解。
- 内部类可以访问其外部类的数据和方法,因此不需要将外部类的引用传递给内部类的构造方法。
- 编译结构
- 内部类的字节码文件以
OuterClassName$InnerClassName.class
的格式生成。例如,OuterClass
类中的内部类InnerClass
会被编译成OuterClass$InnerClass.class
文件。
- 内部类的字节码文件以
匿名内部类(Anonymous Inner Classes)
简化事件处理
匿名内部类是一种没有名称的内部类,通常用于简化代码,尤其是在事件监听器中。
它结合了声明内部类和创建类实例的操作,在一个步骤中完成。
匿名内部类的语法:
1
2
3
4new SuperClassName/InterfaceName() {
// 实现或重写超类或接口中的方法
// 可以包含其他方法
}
编译结果
- 匿名内部类会被编译为
OuterClassName$n.class
,其中n
是类的编号。例如,Test
类中的两个匿名内部类将分别被编译为Test$1.class
和Test$2.class
。
- 匿名内部类会被编译为
通过匿名内部类,可以在事件处理、回调等场景中简化代码,避免创建额外的类定义。
简化事件处理使用 Lambda 表达式
Lambda 表达式简介
Lambda 表达式是 Java 8 中新增的特性。它可以视为一种匿名方法,具有简洁的语法。
Lambda 表达式简化了匿名内部类的代码。例如,以下代码:
- 使用匿名内部类的事件处理器:
1
2
3
4
5
6
7
8btEnlarge.setOnAction(
new EventHandler<ActionEvent>() {
public void handle(ActionEvent e) {
// 处理事件 e 的代码
}
}
);- 使用 Lambda 表达式的事件处理器:
1
2
3btEnlarge.setOnAction(e -> {
// 处理事件 e 的代码
});
Lambda 表达式的基本语法
- Lambda 表达式的基本语法有两种形式:
(type1 param1, type2 param2, ...) -> expression
其中expression
是 Lambda 表达式的主体,可以是一个返回值的表达式。(type1 param1, type2 param2, ...) -> { statements; }
这种形式用于包含多个语句的 Lambda 表达式,语句必须放在大括号{}
内。
- Lambda 表达式的基本语法有两种形式:
参数的类型声明
- 参数的数据类型可以显式声明,也可以由编译器隐式推断。如果参数没有显式数据类型,且只有一个参数,则可以省略括号。
通过 Lambda 表达式,Java 8 极大地简化了事件处理代码,尤其是在处理回调和事件监听器时,代码变得更加简洁和易读。