Java复习-最后的战役

程序化编程与面向对象编程(OOP)

  1. 程序化编程:

    • 主要关注解决问题所需的步骤序列。
    • 编程步骤按顺序执行,通常是一个固定的流程:
      • 步骤1
      • 步骤2
      • 步骤3
      • 步骤4
      • 步骤5
  2. 面向对象编程(OOP):

    • 强调创建和对象之间的交互。

    OOP的优势:

    • 模块化: 对象的源代码可以独立于其他对象的源代码进行编写和维护。一旦创建,对象可以轻松在系统内部传递。
    • 信息隐藏: 通过与对象的方法交互,外界无法了解对象内部实现的细节。
    • 代码重用: 如果某个对象已经存在(可能由其他开发者编写),你可以在自己的程序中使用该对象。这使得专业人员可以实现、测试和调试复杂的任务特定对象,从而在自己的代码中放心使用。
    • 可插拔性和调试方便: 如果发现某个对象存在问题,可以将其从应用程序中移除,并插入一个替代对象。

JVM、JRE 和 JDK

  1. JVM (Java虚拟机)
    • 每个平台上运行程序时都需要Java虚拟机(JVM)。
    • JVM负责解释Java技术代码,加载Java类,并执行Java技术程序。
  2. JRE (Java运行时环境)
    • Java技术程序还需要一组平台特定的标准Java类库。
    • Java类库是预先编写的代码库,可以与您编写的代码结合,创建强健的应用程序。
    • JVM和Java类库结合在一起称为Java运行时环境(JRE)。
  3. JDK (Java开发工具包)
    • Java开发工具包(JDK)包括JRE、Java工具(如javacjavajdb)以及Java API(应用程序接口)。

平台独立的程序

  • 编译后的Java程序的结果格式是平台独立的Java字节码,而不是特定于CPU的机器码。
  • 创建字节码后,它会被一个名为虚拟机(VM)的字节码解释器解释并执行。
  • 虚拟机理解平台独立的字节码,并能够在特定的平台上执行它。

Java程序的结构

  1. 类名
  2. 主方法
  3. 语句
  4. 语句结束符(如分号 ;
  5. 保留字(如 public, class 等)
  6. 注释(用///* */标注)
  7. 代码块(由大括号 {} 包围的代码段)

这些构成了一个标准的Java程序结构。

从控制台读取输入

  1. 标识符
    标识符是用来命名变量、方法、类等的名称。它们由字母、数字、下划线(_)和美元符号($)组成,但不能以数字开头。

  2. 变量
    变量用于存储数据。每个变量都有一个数据类型,用于确定存储在其中的数据的类型。

  3. 声明变量,赋值语句
    声明变量时,需要指定变量的类型并给它一个名字。赋值语句用于将数据赋给已声明的变量。例如:

    1
    int age = 25; // 声明并初始化变量
  4. 声明和初始化一步到位
    可以在声明变量时同时给它赋初值:

    1
    double radius = 3.14; // 声明并初始化
  5. 命名常量
    常量是指值在程序运行期间不可改变的变量,通常使用final关键字来声明:

    1
    final double PI = 3.14159;

数值数据类型和运算符

  1. 整数除法和余数运算符

    • 整数除法:当两个整数相除时,结果会是整数(忽略小数部分)。
    • 余数运算符(%):计算两个数相除后的余数。
  2. 指数运算
    Java没有直接的指数运算符,但可以使用Math.pow()方法进行指数运算。

  3. 数值字面量

    • 整数字面量:表示整数的常量。
    • 浮点字面量:表示浮动小数的常量。
  4. doublefloat的区别

    • double具有更高的精度(64位),适用于需要高精度的小数计算。
    • float精度较低(32位),适用于内存较为紧张的应用。
  5. 类型转换规则
    Java会自动进行“类型提升”,例如将int转换为double,但某些情况下需要手动进行类型转换。


从控制台读取输入

  1. 创建Scanner对象
    要从控制台读取输入,需要使用Scanner类。首先,创建一个Scanner对象:

    1
    Scanner input = new Scanner(System.in);  // 创建一个Scanner对象

    这行代码声明了一个名为inputScanner类型变量,并将System.in(代表标准输入)传给它。

  2. 使用next方法读取输入
    使用Scanner对象的next方法来获取用户输入的值。例如,要读取一个double类型的值:

    1
    2
    3
    System.out.print("Enter a double value: ");
    Scanner input = new Scanner(System.in);
    double radius = input.nextDouble(); // 获取用户输入的double值

    在这个例子中,程序提示用户输入一个double类型的值,并将其存储在radius变量中。

整数除法与取模操作

  1. 整数除法 (/)
    • 当除法的两个操作数均为整数时,结果为商,小数部分被截去。
    • 例如:
      • 5 / 2 的结果是 2,因为小数部分被截去。
      • 5.0 / 2 的结果是 2.5,因为其中一个操作数是浮点数。
  2. 取模操作符 (%)
    • 取模操作符用于获取除法后的余数。
    • 例如:
      • 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) 方法来计算 ab 次方。
    • 例如:
      • 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;
  • 在这些语句中,3420_1010000005.0false 都是字面值。

整型字面值(Integer Literals)

  • 整型字面值可以赋给整型变量,只要它能够容纳这个值。如果字面值过大,超出了变量的范围,就会发生编译错误。例如:
    • byte b = 1000; 会导致编译错误,因为 1000 不能存储在 byte 类型的变量中。
  • 整型字面值默认是 int 类型,它的值范围是从 -2^31(即 -2147483648)到 2^31 - 1(即 2147483647)。
  • 如果要表示一个 long 类型的整数字面值,需要在数字后面加上字母 Ll,通常使用大写字母 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;,将 ab 相乘,并将结果赋值给 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 用于表示逻辑值,只能取 truefalse 两个值。
  • 布尔类型常用于条件判断语句中,例如 ifwhile 等。

条件语句

  1. 单分支 if 语句(One-way if Statement)

    • 语法:如果条件为 true,则执行指定的代码块;否则不执行。
    1
    2
    3
    if (condition) {
    // 执行代码
    }
  2. 双分支 if-else 语句(Two-way if Statement)

    • 语法:如果条件为 true,则执行 if 代码块;否则执行 else 代码块。
    1
    2
    3
    4
    5
    if (condition) {
    // 执行代码
    } else {
    // 执行其他代码
    }
  3. 多分支 if-else if 语句(Multiple Alternative if Statements)

    • 语法:判断多个条件,根据条件选择执行的代码块。
    1
    2
    3
    4
    5
    6
    7
    if (condition1) {
    // 执行代码
    } else if (condition2) {
    // 执行代码
    } else {
    // 执行其他代码
    }
  4. 多分支 if-else if-else 语句(Multi-Way if-else Statements)

    • 语法:使用多个条件分支,根据条件执行不同的代码块。
  5. switch 语句(Switch Statements)

    • 用于判断多个不同的值,并根据匹配的值执行对应的代码块。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    switch (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 变为 falsefalse 变为 true

条件表达式(Conditional Expressions)

  • 条件表达式(也叫三元运算符)用于简化 if-else 语句的写法:

    1
    int result = (condition) ? value1 : value2;

    如果 conditiontrueresultvalue1;否则为 value2


浮点数相等测试的常见错误

  • 由于浮点数计算的有限精度,浮点数之间的相等测试是不可靠的。例如:

    1
    2
    3
    Double 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) 返回一个介于 aa+b-1 之间的随机整数。

ASCII 码与常用字符

  • ASCII 码(美国标准信息交换码)是一个 8 位编码表,用来表示大小写字母、数字、标点符号和控制字符。大多数计算机都使用 ASCII 码。
  • Unicode 码 包含了 ASCII 码,'\u0000''\u007F' 对应 128 个 ASCII 字符。

字符与数值类型的转换(Casting between char and Numeric Types)

  1. 字符到整数类型转换:字符类型 char 可以被直接转换为整数类型 int,并且 char 会转换为其 Unicode 值。

    • 例如:

      1
      int i = 'a'; // 等同于 int i = (int)'a';

      结果:i = 97,字符 'a' 的 Unicode 值是 97。

  2. 整数到字符类型转换:整数可以被转换为字符 char,转换时取该整数值对应的 Unicode 字符。

    • 例如:

      1
      char c = 97; // 等同于 char c = (char)97;

      结果:c = 'a',数字 97 对应的字符是 'a'

  3. 进制转换:字符也可以被转换为其他数值类型,或者通过特定的数值赋给字符类型。转换时只取低 16 位数值。

    • 例如:

      1
      char ch = (char)0XAB0041; // 低 16 位的十六进制码 0041 被赋值给 ch

      结果:ch 会是字符 'A',因为十六进制 0041 对应的字符是 'A'

  4. 浮点数转换为字符类型:当浮点数(如 65.25)转换为 char 类型时,会先将其转换为整数,然后再转换为字符。

    • 例如:

      1
      char c = (char) 65.25;  // 将 65.25 转换为 int,再转换为 char

      结果:c = 'A',因为 65 对应字符 'A'

  5. 字符转换为整数:字符类型可以直接转换为其 Unicode 值。

    • 例如:

      1
      2
      int 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
      3
      String s1 = "abc";
      String s2 = "abg";
      System.out.println(s1.compareTo(s2)); // 输出:-4

      这表示 s1s2 从第一个不同字符开始,ASCII 值的差为 -4。


字符串与数字之间的转换

  1. 字符串转换为数字

    • 要将字符串转换为数字,可以使用 Integer.parseInt()Double.parseDouble() 方法。

      1
      2
      int intValue = Integer.parseInt("123");  // 将字符串 "123" 转换为 int 类型
      double doubleValue = Double.parseDouble("45.67"); // 将字符串 "45.67" 转换为 double 类型
  2. 数字转换为字符串

    • 将数字转换为字符串时,可以使用字符串连接操作符 + 或者使用 String.valueOf()Integer.toString() 方法:

      1
      2
      3
      String s = 23 + "";  // 通过连接操作符将数字 23 转换为字符串 "23"
      String s1 = String.valueOf(s); // 使用 String.valueOf() 方法
      String s2 = Integer.toString(15); // 使用 Integer.toString() 方法
    • 这几种方法都可以将数值转换为字符串。

通过这些方法,Java 提供了便捷的方式在字符串与数值之间进行转换。


实例方法和静态方法

  • 实例方法:字符串是 Java 中的对象,这些方法只能通过特定的字符串实例来调用,因此称为实例方法。

  • 静态方法:静态方法是指不依赖于对象实例,可以直接通过类名来调用的方法。Java 中 Math 类中的所有方法都是静态方法,它们不依赖于任何特定的对象实例。


实例方法与静态方法的比较

  1. 调用实例方法的语法

    • 通过引用变量来调用实例方法:

      1
      Reference-Variable.methodName(arguments);

      示例:

      1
      2
      String message = "Hello Java";
      System.out.println(message.length()); // 调用实例方法 length()
  2. 调用静态方法的语法

    • 直接通过类名来调用静态方法:

      1
      ClassName.methodName(arguments);

      示例:

      1
      Math.pow(2, 3);  // 调用静态方法 pow()

通过这种方式,实例方法需要依赖于特定的对象,而静态方法则可以直接通过类名调用,无需对象实例。

循环语句

  • while 循环:当条件为真时,反复执行循环体中的语句。
  • do-while 循环:首先执行一次循环体,然后检查条件是否满足,若满足则继续执行循环。
  • for 循环:通常用于已知循环次数的情况,通过三个部分(初始化、条件判断、更新)控制循环。

break 语句

break 语句用于跳出整个循环,立即终止循环的执行。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class TestBreak {
public static void main(String[] args) {
int sum = 0;
int number = 0;
while (number < 20) {
number++;
sum += number;
if (sum >= 100) // 当和达到 100 时,跳出循环
break;
}
System.out.println("The number is " + number); // 输出最后的 number 值
System.out.println("The sum is " + sum); // 输出最后的 sum 值
}
}

在这个例子中,当 sum 大于或等于 100 时,break 会跳出 while 循环。


continue 语句

continue 语句用于跳过当前循环中的一次迭代,继续执行下一次迭代。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class TestContinue {
public static void main(String[] args) {
int sum = 0;
int number = 0;
while (number < 20) {
number++;
if (number == 10 || number == 11) // 当 number 等于 10 或 11 时,跳过当前迭代
continue;
sum += number;
}
System.out.println("The sum is " + sum); // 输出 sum 的值
}
}

在这个例子中,当 number 等于 10 或 11 时,continue 会跳过当前的迭代,不执行 sum += number 语句,直接进入下一次循环。


总结:

  • break:终止整个循环,跳出循环外部。
  • continue:跳过当前循环的迭代,直接进入下一次循环。

方法

定义方法 (Defining Methods)

方法是由方法名称、参数、返回值类型、修饰符以及方法体组成。方法的定义语法如下:

1
2
3
修饰符 返回值类型 方法名(参数列表) {
// 方法体
}

例如:

1
2
3
4
5
6
7
8
public static int max(int num1, int num2) {
int result;
if (num1 > num2)
result = num1;
else
result = num2;
return result;
}

在这个例子中:

  • public 是方法的修饰符。
  • static 是静态修饰符,表示该方法属于类本身。
  • int 是返回值类型,表示方法返回一个整数。
  • max 是方法名。
  • (int num1, int num2) 是参数列表,表示方法接受两个整数作为输入。
  • 方法体部分包含实际的代码逻辑。

调用方法 (Calling Methods)

调用方法时,必须传入正确的实参(实际参数),并得到返回值。例如:

1
2
3
4
5
6
public static void main(String[] args) {
int i = 5;
int j = 2;
int k = max(i, j); // 调用max方法,传入i和j作为参数
System.out.println("The maximum between " + i + " and " + j + " is " + k);
}
  • max(i, j) 调用 max 方法,并传入 ij 作为参数。
  • 方法返回值被存储在变量 k 中,并在 System.out.println 中输出。

通过值传递 (Pass by Value)

Java 中的方法传递参数是通过值传递的。这意味着,传入的方法参数是实参的副本,方法内的修改不会影响到实参的值。

例如:

1
2
3
4
5
6
7
8
public static int max(int num1, int num2) {
int result;
if (num1 > num2)
result = num1;
else
result = num2;
return result;
}

尽管方法 max 的形参 num1num2 存储了实参的值,但是它们是独立的变量,修改 num1num2 不会影响调用 max 方法时传入的 ij


方法重载 (Overloading Methods)

方法重载允许使用相同的名称定义多个方法,只要它们的参数列表不同即可。Java 编译器根据方法签名(方法名和参数列表)来决定调用哪个方法。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
public static int max(int num1, int num2) {
if (num1 > num2)
return num1;
else
return num2;
}

public static double max(double num1, double num2) {
if (num1 > num2)
return num1;
else
return num2;
}
  • 这两个 max 方法具有相同的名称,但一个接受 int 类型参数,另一个接受 double 类型参数。
  • 编译器根据传入参数的类型选择正确的重载方法。

数组

介绍数组 (Introducing Arrays)

  1. 声明数组变量 (Declaring Array Variables)
    数组在 Java 中是一个存储相同类型元素的容器。声明数组变量的方式如下:

    1
    type[] arrayName;

    例如:

    1
    int[] numbers;  // 声明一个整数数组
  2. 创建数组 (Creating Arrays)
    创建数组的方式是使用 new 关键字:

    1
    arrayName = new type[size];

    例如:

    1
    numbers = new int[5];  // 创建一个包含 5 个整数的数组
  3. 声明和创建数组 (Declaring and Creating in One Step)
    也可以将数组声明和创建合并为一步:

    1
    int[] numbers = new int[5];  // 声明并创建一个包含 5 个整数的数组
  4. 数组的长度 (The Length of an Array)
    数组的长度可以通过 .length 属性获取:

    1
    int length = numbers.length;  // 获取数组的长度
  5. 默认值 (Default Values)
    在创建数组时,Java 会为数组元素赋予默认值:

    • 整型数组的默认值是 0
    • 布尔型数组的默认值是 false
    • 对象数组的默认值是 null
  6. 使用索引的变量 (Indexed Variables)
    数组的每个元素都有一个索引,可以通过索引访问数组元素。例如:

    1
    numbers[0] = 10;  // 访问数组中的第一个元素并赋值
  7. 简写初始化数组 (Using the Shorthand Notation)
    数组还可以使用简写方式进行初始化:

    1
    int[] numbers = {1, 2, 3, 4, 5};  // 直接初始化数组

处理数组 (Processing Arrays)

  1. 初始化数组 (Initializing Arrays with Input Values)
    数组可以通过输入值进行初始化,或者可以通过 for 循环进行赋值。

  2. 打印数组 (Printing Arrays)
    可以使用 for 循环来打印数组的每个元素:

    1
    2
    3
    for (int i = 0; i < numbers.length; i++) {
    System.out.println(numbers[i]);
    }
  3. 求数组元素和 (Summing all Elements)
    可以通过 for 循环遍历数组并求出所有元素的和:

    1
    2
    3
    4
    int sum = 0;
    for (int i = 0; i < numbers.length; i++) {
    sum += numbers[i];
    }
  4. 找出数组中最大元素 (Finding the Largest Element)
    可以遍历数组,找出最大元素:

    1
    2
    3
    4
    5
    6
    int max = numbers[0];
    for (int i = 1; i < numbers.length; i++) {
    if (numbers[i] > max) {
    max = numbers[i];
    }
    }
  5. 复制数组 (Copying Arrays)
    可以使用 Arrays.copyOfSystem.arraycopy 方法来复制数组。

  6. 将数组传递给方法 (Passing Arrays to Methods)
    数组可以作为参数传递给方法。例如:

    1
    2
    3
    4
    5
    public static void printArray(int[] arr) {
    for (int i = 0; i < arr.length; i++) {
    System.out.println(arr[i]);
    }
    }
  7. 从方法返回数组 (Returning an Array from a Method)
    方法可以返回数组。例如:

    1
    2
    3
    public static int[] createArray() {
    return new int[]{1, 2, 3, 4, 5};
    }

Arrays.sort 方法

Java 提供了 Arrays.sort 方法来对数组进行排序。Arrays.sort 是在 java.util.Arrays 类中定义的,它有多个重载方法,可以对 intdoublecharshortlongfloat 类型的数组进行排序。例如:

1
2
3
4
5
double[] numbers = {6.0, 4.4, 1.9, 2.9, 3.4, 3.5};
java.util.Arrays.sort(numbers); // 对整个数组进行排序

char[] chars = {'a', 'A', '4', 'F', 'D', 'P'};
java.util.Arrays.sort(chars, 0, 3); // 对部分数组排序,从 chars[0] 到 chars[2]

排序后的结果将是:

  • 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
2
3
4
5
6
7
8
9
10
11
12
13
14
public class A {
public static void main(String[] args) {
String[] strings = {"New York", "Boston", "Atlanta"};
B.main(strings); // 调用 B 类的 main 方法
}
}

class B {
public static void main(String[] args) {
for (int i = 0; i < args.length; i++) {
System.out.println(args[i]);
}
}
}

在这个例子中,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)

  1. 构造器 (Constructors) 和 默认构造器 (Default Constructor)
    构造器是一个特殊的方法,它在创建对象时被调用,用于初始化对象的状态。如果没有定义构造器,Java 会提供一个默认构造器,它会初始化对象为默认值。

  2. 声明对象引用变量 (Declaring Object Reference Variables)
    在 Java 中,声明一个对象引用变量的语法如下:

    1
    ClassName objectName;

    例如:

    1
    Circle circle;  // 声明一个 Circle 类型的对象引用
  3. 访问对象的成员 (Accessing Object’s Members)
    通过对象引用变量访问对象的属性和方法:

    1
    2
    objectName.property;  // 访问对象的属性
    objectName.method(); // 调用对象的方法
  4. 引用数据域 (Reference Data Fields)
    类中的数据域可以是实例变量或静态变量,实例变量是与对象实例关联的,而静态变量是与类关联的,所有对象共享同一个静态变量。

  5. 数据域的默认值 (Default Value for a Data Field)
    对于类中的数据字段,Java 会为其提供默认值:

    • 整型变量的默认值是 0
    • 布尔型变量的默认值是 false
    • 对象引用的默认值是 null
  6. 随机类 (The Random Class)
    java.util.Random 类提供了更强大的随机数生成功能。可以使用 nextInt() 方法生成随机的整数:

    1
    2
    Random rand = new Random();
    int randomNum = rand.nextInt(100); // 生成 0 到 99 之间的随机整数

静态变量、常量和方法 (Static Variables, Constants, and Methods)

  1. 静态变量 (Static Variables)
    静态变量是类级别的,所有实例共享同一个静态变量。静态变量通过 static 关键字声明:

    1
    static int count;  // 静态变量
  2. 静态方法 (Static Methods)
    静态方法属于类而不是对象,不能访问实例变量,只能访问静态变量。所有的静态方法都可以通过类名调用:

    1
    Math.max(5, 10);  // 静态方法
  3. 常量 (Constants)
    常量是值不可改变的变量,通常与 finalstatic 关键字一起使用:

    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 关键字是指当前对象的引用。它常用于以下几种情况:

  1. 引用类的隐藏数据字段:当方法的参数与数据字段名称相同时,可以使用 this 来区分它们。

  2. 调用当前类的其他构造器:通过 this() 调用同一个类中的其他构造器:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class Circle {
    private double radius;

    public Circle() {
    this(1.0); // 调用另一个构造器
    }

    public Circle(double radius) {
    this.radius = radius;
    }
    }

包装类

包装类 (Wrapper Classes)

  1. 包装类 (Wrapper Classes)
    在 Java 中,基本数据类型如 intdoublechar 等不能直接作为对象传递到方法中,包装类提供了将这些基本类型包装成对象的功能。Java 为每种基本数据类型提供了对应的包装类:

    • Boolean(包装布尔值)
    • Character(包装字符)
    • Short(包装短整型)
    • Byte(包装字节型)
    • Integer(包装整型)
    • Long(包装长整型)
    • Float(包装单精度浮点型)
    • Double(包装双精度浮点型)

    使用包装类的一个重要优势是可以将基本数据类型作为对象处理,方便进行方法传递和其他操作。

  2. 静态 valueOf 方法
    valueOf(String s) 方法可以根据字符串值创建包装类的对象。例如:

    1
    2
    Double doubleObject = Double.valueOf("12.4");
    Integer integerObject = Integer.valueOf("12");
  3. 基本类型和包装类类型之间的自动转换
    Java 支持基本数据类型和包装类之间的自动转换(自动装箱和自动拆箱)。例如,基本类型的值可以自动装箱成包装类对象,而包装类对象可以自动拆箱成基本类型值:

    1
    2
    Integer[] intArray = {1, 2, 3};  // 自动装箱
    System.out.println(intArray[0] + intArray[1] + intArray[2]); // 自动拆箱并相加

String 类

  1. 构造字符串 (Constructing a String)
    创建字符串的方法有多种,如直接使用双引号创建字符串,或者使用 String 类的构造方法创建。

  2. 获取字符串长度和检索单个字符 (Obtaining String Length and Retrieving Individual Characters)

    • 使用 length() 方法获取字符串的长度。
    • 使用 charAt(int index) 方法获取字符串中指定位置的字符。
  3. 字符串拼接 (String Concatenation)
    使用 concat() 方法拼接字符串。也可以使用 + 运算符直接拼接。


StringBuilder 类和 StringBuffer 类

  1. StringBuilder/StringBuffer 类的介绍
    StringBuilderStringBuffer 类是 String 类的替代方案。这两个类的主要优点是能够修改字符串的内容,而 String 对象一旦创建,其值是不可改变的。StringBuilderStringBufferString 更加灵活,可以在字符串中插入、追加或删除内容。

  2. 修改字符串 (Modifying Strings in StringBuilder)
    StringBuilder 提供了多种方法来修改字符串:

    • append(data: char[]): 将字符数组添加到字符串末尾。
    • insert(index: int, data: char[]): 在指定位置插入字符数组。
    • delete(startIndex: int, endIndex: int): 删除指定范围的字符。
    • reverse(): 将字符串反转。
    • setCharAt(index: int, ch: char): 设置指定位置的字符。
  3. 示例

    1
    2
    3
    StringBuilder stringBuilder = new StringBuilder("Welcome to");
    stringBuilder.append(" Java"); // 追加 "Java"
    System.out.println(stringBuilder.toString()); // 输出 "Welcome to Java"

通过 StringBuilderStringBuffer,可以动态修改字符串内容,适用于需要频繁修改字符串的场景。

继承与多态

使用super关键字

  • 调用父类构造方法

    • 子类无法继承父类的构造方法,只能使用super关键字在子类构造方法中调用父类的构造方法。
    • 调用父类构造方法的语法是:super()super(arguments)
      • super():调用父类的无参构造方法。
      • super(arguments):调用父类的带参数构造方法。
  • super的使用规则

    • super语句必须位于子类构造方法的第一行,这是调用父类构造方法的唯一方式。

    • 例如:

      1
      2
      3
      4
      public CircleFromSimpleGeometricObject(double radius, String color, boolean filled) {
      super(color, filled); // 调用父类构造方法
      this.radius = radius;
      }
    • 在Java中,父类构造方法总是被调用的。如果子类没有显式调用父类构造方法,编译器会自动插入super()

构造方法链(Constructor Chaining)

  • 构造一个类的实例时,会沿着继承链依次调用所有父类的构造方法,直到最终调用到Object类的构造方法。这一过程称为构造方法链(Constructor Chaining)。

  • 例如,子类构造方法会首先调用父类构造方法,如果父类继承自其他类,那么父类的构造方法也会调用其父类的构造方法,依此类推。

  • 例子:

    1
    2
    3
    4
    public A(double d) {
    super(); // 自动调用父类构造方法
    // 其他代码
    }

方法重写(Overriding)

  • 重写方法:子类继承了父类的方法,有时需要修改父类方法的实现,这就是方法重写(Overriding)。

  • 例如:

    1
    2
    3
    4
    5
    6
    public class Circle extends GeometricObject {
    // 重写父类的toString方法
    public String toString() {
    return super.toString() + "\nradius is " + radius;
    }
    }

    在上面的例子中,Circle类重写了GeometricObject类的toString方法。

方法重载(Overloading)与方法重写(Overriding)的区别

  • 方法重载:在同一个类或通过继承关系的类中,定义多个同名但参数类型、个数或顺序不同的方法。

  • 方法重写:在子类中重新实现父类的方法,方法签名必须相同,目的是改变父类方法的行为。

  • 示例:

    • 重写(Overriding)

      1
      2
      3
      4
      5
      class A extends B {
      public void p(double i) {
      System.out.println(i);
      }
      }
    • 重载(Overloading)

      1
      2
      3
      4
      5
      class 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 { ... }
  • toString()方法

    • toString()方法用于返回对象的字符串表示。Object类的默认实现返回的是包含类名、@符号和对象的哈希码的字符串(例如Loan@15037e5)。

    • 通常,建议重写toString()方法,以返回更具可读性和信息性的字符串表示。

    • 例如:

      1
      2
      Loan loan = new Loan();
      System.out.println(loan.toString()); // 输出如Loan@15037e5

多态(Polymorphism)

  • 多态的定义

    • 多态意味着超类类型的变量可以引用子类对象。
    • 例如,CircleGeometricObject的子类,而GeometricObjectCircle的超类。
    • 方法m(Object x)可以接受任何类型的对象,这就体现了多态。
  • 动态绑定

    • 当调用toString()方法时,Java虚拟机(JVM)根据对象的实际类型(例如GraduateStudentStudentPersonObject)动态决定使用哪个类的toString()方法。
    • 这就是动态绑定,在运行时根据对象的实际类型选择方法的实现。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    public 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的实例,同时也是C2C3...等类的实例。在调用方法时,JVM会按照继承链(从C1C2等)查找并执行相应的实现。

对象的转换(Casting)

  • 隐式转换

    • 如果一个子类对象被赋值给父类类型的变量,Java会自动进行类型转换。
    • 例如:Object o = new Student();,这是一种隐式转换,因为StudentObject的子类。
  • 显式转换

    • 当将父类对象转换为子类对象时,必须使用显式转换。例如:

      1
      2
      Fruit fruit = new Apple();
      Apple apple = (Apple) fruit; // 显式转换
  • instanceof操作符

    • 使用instanceof操作符可以测试一个对象是否是某个类的实例。

    • 例如:

      1
      2
      3
      if (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中有四种访问修饰符:publicprotecteddefault(包内访问)和private
  • 各修饰符的访问范围:
    • public:可以在任何地方访问。
    • protected:可以在同一个包内访问,也可以在不同包的子类中访问。
    • default:只有同一个包内的类可以访问。
    • private:只有当前类中的方法和成员可以访问。

异常

异常处理概述

  1. 抛出异常

    • Java中,异常分为两类:必检异常(Checked Exceptions)免检异常(Unchecked Exceptions)
    • RuntimeExceptionError及其子类属于免检异常,不需要强制处理。而其他异常则是必检异常,编译器会强制程序员检查并通过 try-catch 块处理,或者在方法声明中使用 throws 关键字。
  2. 声明异常

    • 每个方法必须声明它可能抛出的必检异常,这就是声明异常。

    • 例如:

      1
      2
      public void myMethod() throws IOException
      public void myMethod() throws IOException, OtherException1, …, OtherExceptionN
  3. 异常处理示例

    • 使用 try-throw-catch 块来处理异常。如果出现异常,会捕获并处理它。
  4. 重新抛出异常

    • 如果异常处理器无法处理异常,或者希望将异常交给调用者处理,Java允许在 catch 块中重新抛出异常。

    • 示例:

      1
      2
      3
      4
      5
      6
      try {
      statements;
      } catch (TheException ex) {
      perform operations before exits;
      throw ex;
      }
    • throw ex 会将异常重新抛出,交由调用者的异常处理器进行处理。

  5. finally 子句

    • 无论是否发生异常,finally 子句中的代码都会被执行。

    • 示例:

      1
      2
      3
      4
      5
      6
      7
      try {
      statements;
      } catch (TheException ex) {
      handling ex;
      } finally {
      finalStatements;
      }
  6. 捕获异常

    • 异常在 try-catch 块中捕获并处理。每种异常可以有不同的处理器。

    • 示例:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      try {
      statements; // 可能抛出异常的语句
      } catch (Exception1 exVar1) {
      handler for exception1;
      } catch (Exception2 exVar2) {
      handler for exception2;
      } ...
      catch (ExceptionN exVar3) {
      handler for exceptionN;
      }
  7. 自定义异常类

    • 如果需要传递更详细的异常信息,可以定义自己的异常类。例如,当半径为负值时,可以抛出一个自定义的异常。

文本I/O

  • File 类封装了文件或路径的属性,但不包含读写数据的方法。
  • 若要执行I/O操作,需要使用适当的 Java I/O 类创建对象,使用这些对象提供的方法来读取或写入数据。
  • 本节介绍了如何使用 Scanner 类读取数据,使用 PrintWriter 类写入数据。

使用 PrintWriter 写入数据

PrintWriter 类用于向文件写入数据,支持不同类型的数据输出:

  1. PrintWriter 构造方法
    • PrintWriter(filename: String):创建一个用于写入指定文件的 PrintWriter 对象。
  2. 常用方法
    • print(s: String):写入字符串。
    • print(c: char):写入字符。
    • print(cArray: char[]):写入字符数组。
    • print(i: int):写入整数。
    • print(l: long):写入长整数。
    • print(f: float):写入浮点数。
    • print(d: double):写入双精度浮点数。
    • print(b: boolean):写入布尔值。
  3. println 方法
    • println 方法类似于 print 方法,但在输出后会添加一个行分隔符。行分隔符由系统定义:
      • Windows:\r\n
      • Unix/Linux:\n
  4. printf 方法
    • printf 方法用于格式化输出,已在第3.6节“格式化控制台输出和字符串”中介绍。

自动关闭资源的 try-with-resources 语法

在 JDK 7 中,引入了 try-with-resources 语法,帮助程序员自动关闭资源(如文件),避免忘记关闭文件导致资源泄露。

  1. 语法

    1
    2
    3
    try (declare and create resources) {
    // 使用资源处理文件
    }
    • try-with-resources 语法会自动关闭在 try 块中声明和创建的资源,无需显式调用 close() 方法。这减少了资源管理的复杂性,并确保资源最终会被关闭。

示例: 使用 PrintWriter 写入数据并自动关闭文件:

1
2
3
4
5
try (PrintWriter writer = new PrintWriter("output.txt")) {
writer.println("Hello, world!");
} catch (IOException e) {
e.printStackTrace();
}

在这个例子中,PrintWriter 对象会在 try 块结束时自动关闭,无需显式调用 close()

抽象类与抽象方法

抽象类与抽象方法

  1. 抽象类与接口的对比

    • 接口(Interface) 支持多重继承,而 抽象类(Abstract Class) 只支持单继承。
  2. 接口与抽象类的区别

    • 在接口中,数据必须是常量;而在抽象类中,可以有各种类型的数据。
    • 接口中的每个方法只有方法签名,没有实现;而抽象类可以有具体的方法实现。
  3. 实例与类型

    • 假设 cClass2 的实例,c 也是 ObjectClass1Interface1Interface1_1Interface1_2Interface2_1Interface2_2 的实例。
    • 所有类共享一个共同的根类 Object,但是接口没有单一的根接口。接口定义了一个类型,接口类型的变量可以引用任何实现了该接口的类的实例。
  4. 定义接口

    • Java使用以下语法来定义接口:

      1
      2
      3
      4
      public interface InterfaceName {
      constant declarations;
      abstract method signatures;
      }
    • 一个接口可以继承多个接口;例如:interface a extends cls1, cls2;

    • 一个类可以实现多个接口;例如:class b implements face1, face2;

    • 但是,一个类只能继承一个类,这是Java的继承特点。

  5. Comparable接口

    • Comparable 接口定义了 compareTo 方法,用于比较对象的大小。

    • 该接口位于 java.lang 包中:

      1
      2
      3
      public interface Comparable<E> {
      public int compareTo(E o);
      }
    • Comparable 是一个泛型接口,接口中的泛型类型 E 在实现时被替换为具体的类型。compareTo 方法用于判断当前对象相对于给定对象 o 的顺序。当当前对象小于、等于或大于给定对象时,分别返回负整数、零或正整数。

  6. 使用 Comparable 接口进行排序

    • 由于所有实现了 Comparable 接口的对象都有 compareTo 方法,因此,Java API 中的 java.util.Arrays.sort(Object[]) 方法可以使用 compareTo 方法对数组中的对象进行比较和排序。
    • java.util.Arrays.sort(array) 方法要求数组中的元素是 Comparable<E> 的实例。
  7. ComparableRectangle

    • ComparableRectangle 类中,使用 implements 关键字表示该类实现了 Comparable 接口,并实现了接口中的 compareTo 方法。
    • 由于 ComparableRectangle 对象是 Comparable 接口的实例,Java API 中的 Arrays.sort(Object[]) 方法可以通过 compareTo 方法对数组中的 ComparableRectangle 对象进行比较和排序。

Cloneable 接口

  1. Cloneable 接口

    • Cloneable 接口是一个空接口,位于 java.lang 包中:

      1
      2
      3
      package java.lang;
      public interface Cloneable {
      }
    • Cloneable 接口用于标记一个类是可克隆的。它本身没有任何方法,但它的作用是表示一个类具有“可克隆”特征。

  2. 实现 Cloneable 接口

    • 实现 Cloneable 接口的类,其对象可以使用 Object 类中定义的 clone() 方法进行克隆。

    • Object 类的 clone() 方法定义如下:

      1
      protected native Object clone() throws CloneNotSupportedException;
    • 关键字 native 表示该方法在本地平台(JVM)中实现,不是用 Java 编写的。

    • protected 关键字限制了该方法只能在同一包内或子类中访问。通常需要重写 clone() 方法并将其可见性修改为 public,以便在任何包中使用。

  3. 克隆的条件

    • 一个对象能够被克隆,通常需要满足两个条件:
      1. 实现 Cloneable 接口,确保克隆合法,避免抛出 CloneNotSupportedException
      2. 重写 clone() 方法。
  4. 浅拷贝 vs. 深拷贝

    • 浅拷贝(Shallow Copy)

      • 浅拷贝是指仅复制对象本身,包括对象中的基本变量,但不复制对象引用的其他对象。Object 类的 clone() 方法完成的是浅拷贝。

      • 浅拷贝的例子:

        1
        2
        House house1 = new House(1, 1750.50);
        House house2 = (House) house1.clone();
        • house1 == house2 为假,因为它们是不同的对象。
        • house1.whenBuilt == house2.whenBuilt 为真,因为它们引用同一个 Date 对象。
    • 深拷贝(Deep Copy)

      • 深拷贝不仅复制对象本身,还复制对象中引用的所有对象。深拷贝需要手动实现。

      • 深拷贝的例子:

        1
        2
        House house1 = new House(1, 1750.50);
        House house2 = (House) house1.clone();
        • house1 == house2 为假。
        • house1.whenBuilt == house2.whenBuilt 为假,因为它们引用不同的 Date 对象(深拷贝时,whenBuilt 也被克隆了)。
  5. 实现深拷贝

    • 若要进行深拷贝,需要在 clone() 方法中手动克隆引用类型的成员。例如:

      1
      2
      3
      House houseClone = (House) super.clone();
      houseClone.whenBuilt = (java.util.Date) (whenBuilt.clone());
      return houseClone;
    • 这样就确保了 house1house2 拥有不同的 Date 对象引用,从而实现了深拷贝。

JavaFx

JavaFX 基本结构

  1. JavaFX 应用程序的基本结构
    • 每个 JavaFX 应用程序都定义在一个继承自 javafx.application.Application 类的类中。
    • 必须重写 start(Stage) 方法,该方法用于设置应用的界面。
    • JavaFX 应用的核心组成元素是 Stage(舞台)Scene(场景)Nodes(结点)
  2. Stage 和 Scene
    • Stage(舞台):舞台是一个窗口,负责显示应用的场景。当应用启动时,JVM 会自动创建一个主舞台对象(primaryStage),并将其传递给 start 方法。
    • Scene(场景):场景是舞台上的内容区域,它包含了多个结点(如按钮、文本框、标签等)。创建场景时,可以通过 Scene(node, width, height) 构造方法指定场景的大小并添加结点。
    • 结点(Nodes):结点是构成场景的基本元素,包括面板、控件、图形等,像 Button(按钮)就是一个结点。
  3. launch 方法
    • launchApplication 类中的一个静态方法,用于启动独立的 JavaFX 应用程序。
    • 每个 JavaFX 程序必须继承 Application 类,并重写 start 方法。
  4. 创建一个简单的 JavaFX 应用
    • start 方法中,我们通常会创建一个界面元素(如按钮),并将其放入一个 Scene 中,然后将该场景设置到 Stage(主窗口)上进行显示。
    • 示例代码流程:
      1. 重写 start 方法,设置 UI 组件并添加到场景。
      2. 创建按钮 Button 对象,并将其放入 Scene 中。
      3. 设置 Stage 的标题。
      4. 显示 Stage,使应用程序可见。
  5. VBox 布局
    • VBox:是一个布局容器,它会将子结点垂直排列在一个列中。
    • 这种布局方式适用于需要垂直堆叠组件的场景。与 HBox 不同,HBox 会将子结点水平排列。
    • VBox 是一种常用的布局管理器,可以用于创建简单的垂直布局。
  6. FlowPane、HBox 和 VBox
    • FlowPane:可以将子结点布局成多行或多列。
    • HBoxVBox:分别用于将子结点水平或垂直排列,适用于需要单行或单列布局的场景。
  7. 实例分析
    • 在代码中:
      • getHBox() 方法返回一个包含两个按钮和一个图像视图的 HBox 布局容器。
      • getVBox() 方法返回一个包含五个标签的 VBox 布局容器。setMargin 方法用于设置结点之间的外边距,使得布局更为美观。

## Java事件委托类型

Java 事件委派模型

  1. 事件委派模型
    • Java 采用基于 委派模型(the delegation model)来处理事件:
      • 事件源(Event Source):触发事件的对象。
      • 事件处理器(Event Handler / Listener):处理该事件的对象,也叫事件监听器。
      • 事件源触发事件后,事件处理器接收并处理事件。
      • 例如,在 JavaFX 中,事件的处理是通过 EventHandler<T> 接口来实现的,该接口定义了 handle(T e) 方法用于处理事件。
  2. 事件处理流程
    • 事件处理器必须满足以下两个条件:
      1. 监听器对象必须是对应事件处理接口的实例。JavaFX 提供了一个统一的事件处理器接口 EventHandler<T extends Event>
      2. 通过调用 source.setOnXEventType(listener) 方法进行事件监听器的注册。

内部类(Inner Classes)

  1. 定义
    • 内部类(Inner Class)是定义在另一个类中的类。
    • 优势:在某些应用中,使用内部类可以使程序更加简洁和易于理解。
    • 内部类可以访问其外部类的数据和方法,因此不需要将外部类的引用传递给内部类的构造方法。
  2. 编译结构
    • 内部类的字节码文件以 OuterClassName$InnerClassName.class 的格式生成。例如,OuterClass 类中的内部类 InnerClass 会被编译成 OuterClass$InnerClass.class 文件。

匿名内部类(Anonymous Inner Classes)

  1. 简化事件处理

    • 匿名内部类是一种没有名称的内部类,通常用于简化代码,尤其是在事件监听器中。

    • 它结合了声明内部类和创建类实例的操作,在一个步骤中完成。

    • 匿名内部类的语法:

      1
      2
      3
      4
      new SuperClassName/InterfaceName() {
      // 实现或重写超类或接口中的方法
      // 可以包含其他方法
      }
  2. 编译结果

    • 匿名内部类会被编译为 OuterClassName$n.class,其中 n 是类的编号。例如,Test 类中的两个匿名内部类将分别被编译为 Test$1.classTest$2.class

通过匿名内部类,可以在事件处理、回调等场景中简化代码,避免创建额外的类定义。

简化事件处理使用 Lambda 表达式

  1. Lambda 表达式简介

    • Lambda 表达式是 Java 8 中新增的特性。它可以视为一种匿名方法,具有简洁的语法。

    • Lambda 表达式简化了匿名内部类的代码。例如,以下代码:

        1. 使用匿名内部类的事件处理器:
        1
        2
        3
        4
        5
        6
        7
        8
        btEnlarge.setOnAction(
        new EventHandler<ActionEvent>() {
        @Override
        public void handle(ActionEvent e) {
        // 处理事件 e 的代码
        }
        }
        );
        1. 使用 Lambda 表达式的事件处理器:
        1
        2
        3
        btEnlarge.setOnAction(e -> {
        // 处理事件 e 的代码
        });
  2. Lambda 表达式的基本语法

    • Lambda 表达式的基本语法有两种形式:
      1. (type1 param1, type2 param2, ...) -> expression
        其中 expression 是 Lambda 表达式的主体,可以是一个返回值的表达式。
      2. (type1 param1, type2 param2, ...) -> { statements; }
        这种形式用于包含多个语句的 Lambda 表达式,语句必须放在大括号 {} 内。
  3. 参数的类型声明

    • 参数的数据类型可以显式声明,也可以由编译器隐式推断。如果参数没有显式数据类型,且只有一个参数,则可以省略括号。

通过 Lambda 表达式,Java 8 极大地简化了事件处理代码,尤其是在处理回调和事件监听器时,代码变得更加简洁和易读。