对象与类

1. 面向对象程序设计概述

1.1 类

面向对象程序设计(Object-Oriented Programming,OOP)是一种广泛使用的编程范式,它将程序组织为相互作用的对象,以模拟现实世界的实体和概念。面向对象编程的核心思想是将数据(属性)和操作(方法)封装在一个对象中,通过对象之间的交互来实现程序的功能。

以下是面向对象程序设计的一些重要概念:

  1. 类(Class):类是定义对象的模板或蓝图。它描述了对象具有的属性和方法。类可以看作是一种自定义的数据类型。
  2. 对象(Object):对象是类的实例化,是类的具体实体。它具有类定义的属性和方法,并可以通过这些方法进行操作。
  3. 封装(Encapsulation):封装是将数据和操作封装在对象中的机制。对象的内部状态(属性)被隐藏,只能通过对象提供的公共接口(方法)来访问和修改。
  4. 继承(Inheritance):继承是一种机制,通过它一个类可以从另一个类继承属性和方法。继承可以形成类的层次结构,子类可以重用父类的代码并添加自己的特定实现。
  5. 多态(Polymorphism):多态允许不同类的对象对相同的消息做出不同的响应。即使这些对象属于不同的类,但可以通过统一的接口进行操作。

面向对象程序设计的优点包括代码重用性、可扩展性、可维护性和模块化等。它提供了一种结构化的方式来组织和管理复杂的程序,并且更贴近人类的思维方式,使得程序更易于理解和开发。

1.2 类之间的关系

类之间的关系可以通过不同的概念来描述,这些概念包括:

  • 依赖("uses-a")
  • 聚合("has-a")
  • 继承("is-a")
  1. 关联关系(Association):表示两个类之间的静态关联,一个类对象可以与另一个类对象相互关联。关联关系可以是双向的,也可以是单向的。例如,一个学生类与一个班级类之间可以有关联关系,表示学生属于某个班级。
  2. 继承关系(Inheritance):表示一个类是另一个类的子类,子类继承了父类的属性和方法,并可以添加自己的属性和方法。继承关系用于实现类的层次结构,提供了代码重用和多态性的机制。
  3. 实现关系(Implementation):表示一个类实现了一个接口,即该类对接口中定义的方法进行了具体的实现。接口定义了一组规范,实现关系用于表示类遵循了这些规范。
  4. 聚合关系(Aggregation):表示整体与部分之间的关系,整体对象包含部分对象,但部分对象可以独立存在。聚合关系用于表示类之间的整体-部分关系,整体对象可以包含多个部分对象。
  5. 组合关系(Composition):表示整体与部分之间的关系,整体对象包含部分对象,但部分对象不能独立存在,它们的生命周期是相互依赖的。组合关系用于表示类之间的强关联关系,整体对象负责创建和管理部分对象。
  6. 依赖关系(Dependency):表示一个类使用了另一个类的对象作为方法的参数、局部变量或返回值,或者一个类通过调用另一个类的方法来实现自己的功能。依赖关系用于表示类之间的临时性关联,一个类的变化可能会影响到依赖它的类。

1.3 表达类关系的UML记法

动物UML类图

学生上网UML类图

微信支付UML类图

2. 使用预定义类

预定义类是指Java编程语言中已经定义好的类,可以直接在代码中使用而无需自己编写。这些预定义类提供了各种功能和工具,方便开发者进行编程和实现各种任务。以下是一些常用的预定义类和它们的用途:

  1. String:用于操作字符串的类,提供了丰富的字符串处理方法。
  2. Math:提供了数学计算相关的方法,如常用的数学函数、随机数生成等。
  3. Scanner:用于从控制台或文件中读取输入的类,提供了各种方法来解析不同类型的输入数据。
  4. System:提供了与系统交互相关的方法,如标准输入输出、环境变量访问等。
  5. ArrayList:动态数组类,用于存储和操作一组对象。
  6. HashMap:键值对映射类,用于存储和操作键值对数据。
  7. Date:用于表示日期和时间的类,提供了日期和时间的各种操作和格式化方法。

这只是一小部分常用的预定义类,Java还提供了许多其他功能强大的预定义类,用于各种编程任务,如文件操作、网络通信、图形界面等。通过使用这些预定义类,开发者可以更高效地完成各种编程任务,提高开发效率。

2.1 对象与对象变量

对象是类的实例化结果,是类的具体实体,可以通过关键字new来创建对象。对象包含了类中定义的属性和方法,可以通过对象来访问和操作这些属性和方法。

对象变量是用来引用对象的变量,它存储了对象在内存中的地址。通过对象变量,我们可以访问对象的属性和调用对象的方法。对象变量的类型必须与对象的类型相匹配,或者是对象类型的父类或接口类型。

总结起来,对象是类的实例化结果,而对象变量是用来引用对象的变量,通过对象变量我们可以访问和操作对象的属性和方法。

当我们创建一个对象时,实际上是在内存中分配了一块存储对象数据的空间,并返回了该空间的地址。这个地址就是对象在内存中的唯一标识,也被称为对象的引用。我们可以将对象的引用赋值给对象变量,以便后续使用。

public class Person {
    String name;
    int age;

    public void sayHello() {
        System.out.println("Hello, my name is " + name + " and I am " + age + " years old.");
    }
}

public class Main {
    public static void main(String[] args) {
        // 创建Person对象
        Person person1 = new Person();
        person1.name = "Alice";
        person1.age = 25;

        // 创建另一个Person对象
        Person person2 = new Person();
        person2.name = "Bob";
        person2.age = 30;

        // 使用对象变量调用对象的方法
        person1.sayHello(); // 输出:Hello, my name is Alice and I am 25 years old.
        person2.sayHello(); // 输出:Hello, my name is Bob and I am 30 years old.
    }
}

2.2 java类库中的LocalDate类

LocalDate类是Java类库中的一个日期类,它用于表示日期,而不包含时间和时区信息。它是Java 8引入的java.time包中的一部分,用于处理日期相关的操作。

以下是LocalDate类的一些常用方法和功能:

  1. 创建LocalDate对象:可以使用now()方法获取当前日期,也可以使用of()方法指定年、月、日来创建一个具体的日期对象。
  2. 获取日期信息:可以使用getYear()getMonth()getDayOfMonth()等方法获取年、月、日等日期信息。
  3. 格式化日期:可以使用format()方法将日期格式化为指定的字符串,也可以使用DateTimeFormatter类进行自定义的日期格式化。
  4. 比较日期:可以使用isBefore()isAfter()isEqual()等方法比较两个日期的先后关系。
  5. 计算日期:可以使用plusDays()minusMonths()等方法对日期进行加减操作,得到新的日期对象。
  6. 解析日期:可以使用parse()方法将字符串解析为LocalDate对象,根据指定的日期格式进行解析。

下面是一个简单的示例代码,展示了LocalDate类的使用:

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;

public class Main {
    public static void main(String[] args) {
        // 获取当前日期
        LocalDate currentDate = LocalDate.now();
        System.out.println("Current Date: " + currentDate);

        // 创建指定日期
        LocalDate specificDate = LocalDate.of(2022, 10, 31);
        System.out.println("Specific Date: " + specificDate);

        // 格式化日期
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
        String formattedDate = specificDate.format(formatter);
        System.out.println("Formatted Date: " + formattedDate);

        // 比较日期
        LocalDate futureDate = specificDate.plusYears(1);
        System.out.println("Is Future Date after Specific Date? " + futureDate.isAfter(specificDate));

        // 解析日期
        LocalDate parsedDate = LocalDate.parse("2022-12-25", formatter);
        System.out.println("Parsed Date: " + parsedDate);
    }
}

2.3 更改器方法和访问器方法

更改器方法(Mutator Methods)和访问器方法(Accessor Methods)是面向对象编程中用于操作对象属性的常见方法。

  1. 更改器方法(Mutator Methods):也称为设置方法(Setter Methods),用于修改对象的属性值。它们通常被定义为公共的实例方法,接受一个或多个参数,将参数的值赋给对象的属性。更改器方法通常使用set作为方法名的前缀,后面跟着属性的名称。例如,如果有一个名为name的属性,相应的更改器方法可能被命名为setName(String name)
public class Person {
    private String name;

    public void setName(String name) {
        this.name = name;
    }
}
  1. 访问器方法(Accessor Methods):也称为获取方法(Getter Methods),用于获取对象的属性值。它们通常被定义为公共的实例方法,不接受任何参数,返回属性的值。访问器方法通常使用get作为方法名的前缀,后面跟着属性的名称。例如,如果有一个名为name的属性,相应的访问器方法可能被命名为getName()
public class Person {
    private String name;

    public String getName() {
        return name;
    }
}

通过使用更改器方法和访问器方法,可以控制对象属性的访问和修改方式。更改器方法允许对属性进行修改,并提供了封装和数据校验的机制。访问器方法允许以安全的方式获取属性的值,而无需直接访问对象的属性。

使用更改器方法和访问器方法的好处包括:

  • 封装属性,隐藏实现细节,提供更好的数据安全性和一致性。
  • 提供了对属性的控制和验证机制,例如可以在更改器方法中添加条件判断或数据校验逻辑。
  • 支持面向对象的编程范式,通过使用方法来操作属性,而不是直接操作属性本身。

3自定义类

3.1 Employee类

如何定义一个自定义的Java类Employee,该类表示雇员的属性和行为:

public class Employee {
    private String name;
    private int age;
    private double salary;

    public Employee(String name, int age, double salary) {
        this.name = name;
        this.age = age;
        this.salary = salary;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public double getSalary() {
        return salary;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }

    public void displayInfo() {
        System.out.println("Name: " + name);
        System.out.println("Age: " + age);
        System.out.println("Salary: " + salary);
    }
}

在上面的示例中,Employee类具有以下属性:

  • name:表示雇员的姓名,使用String类型。
  • age:表示雇员的年龄,使用int类型。
  • salary:表示雇员的薪水,使用double类型。

该类还包含了一些访问器方法和更改器方法,用于获取和修改属性的值。例如,getName()setName()方法用于获取和设置name属性的值。

除了属性和访问器方法/更改器方法之外,该类还定义了一个displayInfo()方法,用于显示雇员的信息,将姓名、年龄和薪水输出到控制台。

可以通过创建Employee类的实例来使用它,如下所示:

Employee employee = new Employee("John Doe", 30, 5000.0);
employee.displayInfo();

employee.setSalary(6000.0);
System.out.println("Updated Salary: " + employee.getSalary());

上述代码创建了一个名为employeeEmployee对象,并设置了姓名、年龄和薪水。然后调用displayInfo()方法显示雇员的信息。接下来,使用setSalary()方法将薪水更新为6000.0,并使用getSalary()方法获取更新后的薪水值。

这只是一个简单的示例,展示了如何定义和使用自定义的Java类Employee。实际中,可以根据需求添加更多的属性和方法来扩展该类的功能。

3.2 从构造器开始

Java类的构造器(Constructor)是一种特殊的方法,用于创建类的对象并初始化其属性。构造器与类名相同,并且没有返回类型(包括void)。构造器可以具有参数,也可以没有参数。

下面是一个示例,展示了如何定义一个带有参数的构造器:

public class Employee {
    private String name;
    private int age;

    // 带有参数的构造器
    public Employee(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // 访问器方法
    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }
}

在上述示例中,Employee类具有一个带有两个参数(nameage)的构造器。构造器将传入的参数分配给类的属性。

可以通过创建类的对象并调用构造器来使用它,如下所示:

Employee employee = new Employee("John Doe", 30);
System.out.println("Name: " + employee.getName());
System.out.println("Age: " + employee.getAge());

上述代码创建了一个名为employeeEmployee对象,并通过构造器将姓名和年龄设置为"John Doe"和30。然后,可以使用访问器方法getName()getAge()获取对象的属性值。

除了带有参数的构造器,还可以定义无参数的构造器,用于创建对象并进行默认的初始化操作。如果没有显式定义构造器,Java会提供一个默认的无参数构造器。但是,如果显式定义了带有参数的构造器,则需要根据需要自行定义无参数构造器。

构造器在创建对象时自动调用,并且可以用于执行初始化操作,例如分配内存空间、设置默认值等。构造器还可以被重载,即定义多个具有不同参数的构造器,以提供不同的初始化方式。

构造器是Java类中重要的组成部分,它使得对象的创建和初始化变得简单和方便。

3.3 用var声明局部变量

在Java 10及更高版本中,引入了var关键字,用于声明局部变量的类型推断。使用var关键字可以使代码更简洁,减少了冗余的类型声明。

以下是一个示例,展示了如何使用var声明局部变量:

var name = "John Doe";
var age = 30;
var salary = 5000.0;

System.out.println("Name: " + name);
System.out.println("Age: " + age);
System.out.println("Salary: " + salary);

在上述代码中,变量nameagesalary被使用var关键字声明,并根据赋值的类型进行类型推断。编译器会根据右侧的赋值表达式推断出变量的类型。

需要注意的是,var关键字只能用于局部变量的声明,不能用于成员变量、方法参数、方法返回类型或静态变量的声明。此外,使用var声明的变量必须在声明时进行初始化。

在使用var声明局部变量时,仍然建议给变量指定明确的名称,以增加代码的可读性和可维护性。适当使用var关键字可以使代码更简洁,但过度使用可能导致代码可读性下降。

3.4 使用null引用

在Java中,可以使用null值来表示一个引用变量的空值或缺失值。将引用变量设置为null意味着它不引用任何对象。

以下是一些关于使用null引用的注意事项:

  1. 初始化引用变量:如果没有为引用变量提供初始化值,它将自动被初始化为null

    String name = null;
    
  2. 检查引用是否为null:在使用引用变量之前,最好先检查它是否为null,以避免空指针异常(NullPointerException)。

    if (name != null) {
        // 执行操作
    }
    
  3. 避免使用null引用进行操作:尽量避免在引用变量为null时对其进行操作,因为这会导致空指针异常。应该在使用引用之前确保它不为null

  4. 使用Optional类进行空值处理:在Java 8及更高版本中,可以使用Optional类来处理可能为null的值,以避免空指针异常。Optional提供了一些方法来处理可能为空的值,例如orElseorElseGetorElseThrow等。

虽然null引用在Java中是一种常见的表示空值的方式,但需要小心使用,以避免潜在的空指针异常。在编写代码时,请注意检查引用是否为null,并根据需要采取适当的处理措施。

3.5 隐式参数和显式参数

隐式参数和显式参数是方法调用时传递给方法的参数的两种不同方式。

  1. 隐式参数:隐式参数是指在方法调用时,通过方法所属的对象来传递的参数。这些参数可以直接访问方法所在对象的成员变量或方法,不需要显式地传递给方法。隐式参数在方法体内部可以直接使用,无需在方法签名中声明。

示例:

public class MyClass {
    private int count;

    public void increment() {
        count++; // 使用隐式参数访问成员变量count
    }
}

在上面的示例中,increment方法使用隐式参数来访问成员变量count,无需在方法签名中声明参数。

  1. 显式参数:显式参数是指在方法调用时,通过传递具体的参数值来显式地传递给方法。这些参数在方法调用时需要在方法签名中声明,并且在方法体内部使用时需要使用相应的参数名称。

示例:

public class MyClass {
    public void printMessage(String message) {
        System.out.println(message); // 使用显式参数打印消息
    }
}

在上面的示例中,printMessage方法使用显式参数message来打印指定的消息。

总结:

  • 隐式参数是通过方法所属的对象来传递的,可以直接访问对象的成员变量或方法。
  • 显式参数是通过具体的参数值来传递的,需要在方法签名中声明参数,并且在方法体内部使用参数名称来访问参数值。

3.6 基于类的访问权限

基于类的访问权限用于控制类的成员(字段、方法、构造器)在其他类中的可见性和访问权限。Java提供了四种访问权限修饰符来实现不同级别的访问控制:

  1. public:公共访问权限,表示对所有类可见。一个公共成员可以在任何类中访问。
  2. protected:受保护访问权限,表示对同一包中的类和该类的子类可见。受保护的成员可以在同一包中的其他类以及子类中访问。
  3. default(默认):默认访问权限,表示对同一包中的类可见。如果没有指定访问权限修饰符,则默认为默认访问权限。默认访问权限的成员可以在同一包中的其他类中访问。
  4. private:私有访问权限,表示只有在同一类中可见。私有成员只能在声明它们的类内部访问。

这些访问权限修饰符可以应用于类的成员,以控制其他类对这些成员的访问权限。使用适当的访问权限修饰符可以实现封装性、数据隐藏和安全性。

以下是一些示例:

public class MyClass {
    public int publicField;
    protected int protectedField;
    int defaultField;
    private int privateField;

    public void publicMethod() {
        // 公共方法的实现
    }

    protected void protectedMethod() {
        // 受保护方法的实现
    }

    void defaultMethod() {
        // 默认方法的实现
    }

    private void privateMethod() {
        // 私有方法的实现
    }
}

在上面的示例中,publicFieldpublicMethod具有公共访问权限,可以在任何类中访问。protectedFieldprotectedMethod具有受保护访问权限,可以在同一包中的其他类和子类中访问。defaultFielddefaultMethod具有默认访问权限,可以在同一包中的其他类中访问。privateFieldprivateMethod具有私有访问权限,只能在MyClass类内部访问。

请注意,访问权限修饰符的选择应根据设计需求和封装性原则进行,以确保代码的安全性和可维护性。

3.7 final实例字段

在Java中,final关键字可以用于声明实例字段。当实例字段被声明为final时,表示该字段的值在初始化后不能被修改。

以下是使用final关键字声明实例字段的示例:

public class MyClass {
    private final int id;
    private final String name;

    public MyClass(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public String getName() {
        return name;
    }
}

在上面的示例中,idname字段被声明为final,并在构造函数中进行初始化。一旦初始化完成,它们的值就不能再被修改。

使用final实例字段的主要目的是为了确保字段的值在对象的整个生命周期内保持不变,增加代码的可读性和安全性。它可以防止字段被意外修改,同时也可以使代码更易于理解和维护。

请注意,如果一个类被声明为final,则该类的所有字段都隐式地成为final字段,即不能被修改。


4. 静态字段与静态方法

4.1静态字段

静态字段(Static Fields)是属于类本身的字段,而不是类的实例。它们在类加载时被初始化,并且在整个程序的执行过程中只有一份副本。

以下是使用静态字段的示例:

public class MyClass {
    private static int count;
    private static final String PREFIX = "ID";

    public static void incrementCount() {
        count++;
    }

    public static int getCount() {
        return count;
    }

    public static String getPrefix() {
        return PREFIX;
    }
}

在上面的示例中,countPREFIX字段都被声明为静态字段。count用于记录实例的数量,并且可以通过静态方法incrementCount()getCount()进行增加和获取。PREFIX是一个静态常量,它在声明时被初始化,并且在整个程序中保持不变。

静态字段可以通过类名直接访问,不需要创建类的实例。例如,可以使用MyClass.getCount()来获取count的值,或者使用MyClass.incrementCount()来增加count的值。

需要注意的是,静态字段在内存中只有一份副本,它们被所有类的实例共享。因此,对静态字段的修改将影响所有对象的访问。此外,静态字段在多线程环境下需要注意同步和线程安全性。

4.2 静态常量

静态常量(Static Constants)是指被声明为静态的、不可变的常量字段。它们在类加载时被初始化,并且在整个程序的执行过程中保持不变。

以下是使用静态常量的示例:

public class Constants {
    public static final int MAX_COUNT = 100;
    public static final String DEFAULT_NAME = "John Doe";
}

在上面的示例中,MAX_COUNTDEFAULT_NAME都被声明为静态常量。它们被声明为public,表示可以被其他类直接访问。final关键字用于确保这些常量的值在初始化后不能被修改。

静态常量可以通过类名直接访问,不需要创建类的实例。例如,可以使用Constants.MAX_COUNT来获取MAX_COUNT的值,或者使用Constants.DEFAULT_NAME来获取DEFAULT_NAME的值。

静态常量通常用于表示不会改变的固定值,例如常用的数学常数、默认配置等。它们的命名通常使用大写字母和下划线,以提高可读性,并明确表示它们是常量而不是变量。

需要注意的是,静态常量在内存中只有一份副本,它们被所有类的实例共享,并且在整个程序的执行过程中保持不变。因此,静态常量在多个对象之间具有相同的值,并且对静态常量的修改是不允许的。

4.3 静态方法

静态方法(Static Methods)是属于类而不是对象的方法。这意味着可以直接通过类名调用静态方法,而无需创建类的实例。

以下是定义和使用静态方法的示例:

public class MathUtils {
    public static int add(int a, int b) {
        return a + b;
    }

    public static double multiply(double a, double b) {
        return a * b;
    }
}

public class Main {
    public static void main(String[] args) {
        int sum = MathUtils.add(5, 3); // 调用静态方法add
        System.out.println("Sum: " + sum);

        double product = MathUtils.multiply(2.5, 4.0); // 调用静态方法multiply
        System.out.println("Product: " + product);
    }
}

在上面的示例中,MathUtils类包含两个静态方法addmultiply。这些方法使用static关键字进行修饰,使它们成为静态方法。可以直接通过类名调用这些静态方法,如MathUtils.add(5, 3)MathUtils.multiply(2.5, 4.0)

静态方法具有以下特点:

  • 可以在没有创建类的实例的情况下直接调用。
  • 静态方法只能访问类中的静态成员(静态字段和静态方法),不能访问非静态成员。
  • 静态方法不能使用关键字this引用当前对象,因为它们不属于任何对象。
  • 静态方法可以在其他非静态方法中调用,但非静态方法不能直接调用静态方法,需要通过类名进行调用。
  • 静态方法通常用于执行与类相关的操作,例如数学运算、工具方法等,而不依赖于特定的对象状态。

需要注意的是,静态方法不可被子类重写(Override),而是被隐藏(Hidden)。也就是说,子类可以定义与父类中的静态方法具有相同签名的静态方法,但并不会重写父类的静态方法。

4.4 工厂方法

静态方法还有另外一种常见用途,工厂方法(Factory Method)是一种创建对象的设计模式,它通过定义一个公共的静态方法来创建和返回对象实例,而不是直接使用构造函数来实例化对象。工厂方法模式提供了一种封装和灵活性,使得可以根据不同的条件或参数来创建不同类型的对象。

下面是一个简单的示例,展示了如何使用工厂方法创建对象:

public interface Shape {
    void draw();
}

public class Circle implements Shape {
    @Override
    public void draw() {
        System.out.println("Drawing a circle");
    }
}

public class Rectangle implements Shape {
    @Override
    public void draw() {
        System.out.println("Drawing a rectangle");
    }
}

public class ShapeFactory {
    public static Shape createShape(String type) {
        if (type.equalsIgnoreCase("circle")) {
            return new Circle();
        } else if (type.equalsIgnoreCase("rectangle")) {
            return new Rectangle();
        } else {
            throw new IllegalArgumentException("Unsupported shape type");
        }
    }
}

public class Main {
    public static void main(String[] args) {
        Shape circle = ShapeFactory.createShape("circle");
        circle.draw(); // 输出:Drawing a circle

        Shape rectangle = ShapeFactory.createShape("rectangle");
        rectangle.draw(); // 输出:Drawing a rectangle
    }
}

在上面的示例中,Shape是一个接口,定义了图形对象的共同行为。CircleRectangle是实现了Shape接口的具体图形类。ShapeFactory是一个工厂类,它定义了一个静态方法createShape,根据传入的参数来创建相应的图形对象。

通过调用ShapeFactory.createShape方法并传入相应的参数,可以创建并获取不同类型的图形对象,而无需直接调用构造函数。这样可以将对象的创建逻辑封装在工厂类中,提供了更好的可维护性和扩展性。工厂方法模式还可以根据需要对创建过程进行自定义和扩展,例如根据不同的条件返回不同的实例对象。

工厂方法模式在实际开发中常用于创建复杂对象、隐藏对象创建细节、解耦对象之间的依赖关系等场景。

4.5 main方法

在Java中,main方法是程序的入口点,它是Java程序执行的起始点。每个独立的Java程序都必须包含一个main方法,它具有以下特点:

  1. main方法是一个静态方法,因此它属于类而不是对象。

  2. main方法的签名必须满足以下格式:
    
    public static void main(String[] args)
    

    其中,public表示main方法是公共的,可以从其他类访问;static表示main方法是静态的,不依赖于类的实例;void表示main方法不返回任何值;String[] arg表示main方法接受一个字符串数组作为参数,用于接收命令行传递的参数。

  3. main方法是程序的起始点,当程序运行时,JVM会从main方法开始执行。

  4. main方法中的代码定义了程序的执行逻辑,可以包含任意的Java语句和代码块。

下面是一个简单的示例,展示了一个典型的main方法的用法:

public class HelloWorld {
    public static void main(String[] args) {
        // 在控制台输出"Hello, World!"
        System.out.println("Hello, World!");
    }
}

在上面的示例中,HelloWorld是一个Java类,它包含了一个静态的main方法。当程序运行时,JVM会自动查找并执行HelloWorld类中的main方法。在main方法中,通过调用System.out.println方法,在控制台输出了字符串"Hello, World!"。

通过编写main方法,可以定义程序的入口逻辑,执行初始化操作,调用其他类和方法,以及处理命令行参数等。main方法是Java程序的重要组成部分,它使得程序可以独立运行,并提供了与用户交互的入口。


5. 方法参数

方法参数是用于传递数据给方法的值或引用。方法参数允许您将数据从调用方法的代码传递给方法中进行处理或操作。

Java中的方法参数有以下几种类型:

5.1 值参数

值参数(Value Parameters):基本数据类型(如intdoubleboolean等)作为值参数传递给方法时,传递的是实际的值副本,对方法内部的操作不会影响原始值。

public void printNumber(int num) {
    System.out.println(num);
}

5.2 引用参数

引用参数(Reference Parameters):引用类型(如对象、数组等)作为引用参数传递给方法时,传递的是引用的副本,方法内部对引用的操作会影响原始对象。

public void updateName(Student student) {
    student.setName("John");
}

5.3 可变参数

可变参数(Variable Arguments):Java 5及更高版本引入了可变参数,允许方法接受不定数量的参数。可变参数使用数组来表示,方法内部可以将其视为一个数组进行处理。

public void sumNumbers(int... numbers) {
    int sum = 0;
    for (int num : numbers) {
        sum += num;
    }
    System.out.println("Sum: " + sum);
}

在Java中,对象引用的传递方式是按值传递,而不是按引用调用。

当将一个对象传递给一个方法时,实际上是将对象引用的副本传递给方法。这意味着方法中的参数接收的是对象引用的副本,而不是原始的对象引用本身。

当方法中对对象引用进行操作时,操作的是对象本身,而不是传递的引用副本。因此,如果在方法中修改了对象的状态或属性,那么调用方法后,原始对象的状态也会发生改变。但是,如果在方法中将对象引用重新赋值,将不会影响原始对象引用的值。

下面是一个示例来说明这个概念:

class Person {
    String name;

    public Person(String name) {
        this.name = name;
    }
}

public class Main {
    public static void changeName(Person person) {
        person.name = "Alice"; // 修改对象的属性
        person = new Person("Bob"); // 将对象引用重新赋值
    }

    public static void main(String[] args) {
        Person person = new Person("John");
        System.out.println(person.name); // 输出 "John"

        changeName(person);

        System.out.println(person.name); // 输出 "Alice"
    }
}

在上面的示例中,changeName方法接收一个Person对象引用,并修改了对象的属性name为"Alice"。但是在方法内部将对象引用重新赋值为一个新的Person对象,名为"Bob"。然而,这个重新赋值并不会影响到main方法中的原始对象引用。

因此,可以看出对象引用的传递方式是按值传递,对于对象本身的修改会反映在原始对象上,但对于引用的重新赋值不会影响原始对象引用的值。

6. 对象构造

6.1 重载

对象构造函数的重载指的是在同一个类中定义多个具有不同参数的构造函数。

通过构造函数的重载,可以在创建对象时根据不同的需求选择合适的构造函数来初始化对象的属性。

构造函数的重载需要满足以下条件:

  1. 构造函数的名称必须与类名完全相同。
  2. 构造函数的参数列表必须不同,可以有不同的参数类型、参数个数或参数顺序。
  3. 构造函数可以有不同的访问修饰符,例如public、private、protected或默认修饰符。

下面是一个示例,展示了如何在Java中重载构造函数:

class Person {
    String name;
    int age;

    // 无参构造函数
    public Person() {
        name = "Unknown";
        age = 0;
    }

    // 带参数的构造函数
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

public class Main {
    public static void main(String[] args) {
        Person person1 = new Person(); // 使用无参构造函数
        System.out.println(person1.name); // 输出 "Unknown"
        System.out.println(person1.age); // 输出 0

        Person person2 = new Person("John", 25); // 使用带参数的构造函数
        System.out.println(person2.name); // 输出 "John"
        System.out.println(person2.age); // 输出 25
    }
}

在上面的示例中,Person类定义了两个构造函数:一个无参构造函数和一个带参数的构造函数。通过不同的构造函数,可以创建具有不同属性值的Person对象。

6.2 默认字段初始化

字段(成员变量)可以有默认的初始值,如果没有显式地赋予初始值,Java会为其提供一个默认值。默认值的类型取决于字段的数据类型。

以下是Java中一些常见数据类型的默认字段初始化值:

  1. 数值类型(byte、short、int、long、float、double):默认值为0。
  2. 字符类型(char):默认值为'\u0000',即空字符。
  3. 布尔类型(boolean):默认值为false。
  4. 引用类型(类、接口、数组等):默认值为null。

注意,局部变量(在方法内部声明的变量)没有默认初始化值,必须显式地进行赋值才能使用。

6.3 无参数的构造器

无参数的构造器(默认构造器)是一个没有任何参数的构造器方法,它可以在创建对象时使用,并且没有显式定义任何参数。如果在类中没有显式定义构造器,Java会自动提供一个无参数的默认构造器。

以下是一个示例代码,展示了无参数的构造器的用法:

public class MyClass {
    private int myField;

    // 无参数的构造器
    public MyClass() {
        // 初始化字段
        myField = 0;
    }

    public int getMyField() {
        return myField;
    }

    public static void main(String[] args) {
        // 使用无参数的构造器创建对象
        MyClass myObject = new MyClass();
        System.out.println(myObject.getMyField());  // 输出 0
    }
}

在上述示例中,MyClass类中的无参数构造器用于创建对象,并初始化myField字段为0。通过调用无参数构造器创建的对象可以访问和操作该类的其他成员。

需要注意的是,如果在类中已经显式定义了其他构造器(带参数的构造器),则无参数的构造器不会自动提供。在这种情况下,如果需要使用无参数的构造器,必须显式地定义它。

6.4 显式字段初始化

显式字段初始化是指在声明字段时直接给字段赋初值,而不是在构造器或方法中进行赋值操作。这样可以在创建对象时立即给字段赋予指定的初始值。

以下是一个示例代码,展示了显式字段初始化的用法:

public class MyClass {
    private int myField = 10;  // 显式字段初始化

    public int getMyField() {
        return myField;
    }

    public static void main(String[] args) {
        MyClass myObject = new MyClass();
        System.out.println(myObject.getMyField());  // 输出 10
    }
}

在上述示例中,MyClass类中的myField字段在声明时通过赋值操作直接进行了初始化,其初值为10。当创建MyClass对象时,该字段的初始值即为10。

需要注意的是,显式字段初始化只能在字段声明时进行,而不能在构造器、方法或其他代码块中进行赋值操作。如果需要在构造器或方法中进行字段赋值,可以使用构造器或方法参数来接收外部的值,并在构造器或方法中对字段进行赋值操作。

6.5 调用另一个构造器

在Java中,可以使用关键字this来调用同一个类的其他构造器。通过在构造器内部使用this关键字,可以调用同一个类的其他构造器,并传递参数进行初始化操作。

以下是一个示例代码,演示了如何调用另一个构造器:

public class MyClass {
    private int x;
    private int y;

    public MyClass() {
        this(0, 0);  // 调用另一个构造器并传递默认值
    }

    public MyClass(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public int getX() {
        return x;
    }

    public int getY() {
        return y;
    }

    public static void main(String[] args) {
        MyClass obj1 = new MyClass();  // 调用无参构造器
        System.out.println(obj1.getX());  // 输出 0
        System.out.println(obj1.getY());  // 输出 0

        MyClass obj2 = new MyClass(10, 20);  // 调用带参构造器
        System.out.println(obj2.getX());  // 输出 10
        System.out.println(obj2.getY());  // 输出 20
    }
}

在上述示例中,MyClass类定义了两个构造器:一个无参构造器和一个带参构造器。在无参构造器中使用this(0, 0)调用带参构造器,并传递默认值进行初始化。这样,在调用无参构造器创建对象时,实际上会调用带参构造器,并将默认值传递给它。

通过使用this关键字调用另一个构造器,可以在不重复代码的情况下,实现构造器之间的逻辑复用。

this是一个特殊的关键字,它代表当前对象的引用。每个实例方法和构造器都有一个隐含的this参数,指向调用该方法或构造器的对象。

this关键字可以用于以下情况:

  1. 访问当前对象的成员变量:通过this.变量名可以访问当前对象的成员变量。
  2. 调用当前对象的其他方法:通过this.方法名()可以调用当前对象的其他方法。
  3. 在构造器中调用同一类的其他构造器:通过this()this(参数列表)可以调用同一类的其他构造器。

使用this关键字可以帮助区分成员变量和局部变量同名的情况,确保访问的是成员变量而不是局部变量。

6.6 初始化块

初始化块(Initialization Block)是一段代码块,用于在创建对象时执行额外的初始化操作。它是类的成员,位于类的主体中,没有任何修饰符或方法名。

Java中有两种类型的初始化块:静态初始化块(Static Initialization Block)和实例初始化块(Instance Initialization Block)。

  1. 静态初始化块:使用关键字static定义的初始化块,用于初始化静态成员变量。它在类加载时执行,只执行一次。
public class MyClass {
    static {
        // 静态初始化块
        // 初始化静态成员变量
    }
}
  1. 实例初始化块:没有使用任何关键字定义的初始化块,用于初始化实例成员变量。每次创建对象时,实例初始化块都会在构造器之前执行。
public class MyClass {
    {
        // 实例初始化块
        // 初始化实例成员变量
    }
}

初始化块在代码编写顺序上位于构造器之前,可以用于执行一些复杂的初始化逻辑,或者对成员变量进行一些预处理操作。当有多个初始化块时,它们按照编写顺序依次执行。

6.7 对象析构与finalize方法

对象的析构是指对象被销毁或回收时执行的操作。Java提供了一种名为finalize()的特殊方法,它允许对象在被垃圾回收之前执行一些清理操作。

finalize()方法是定义在Object类中的一个实例方法,所有的类都继承了Object类,因此可以重写finalize()方法以实现自定义的清理逻辑。这个方法的定义如下:

protected void finalize() throws Throwable {
    // 清理操作的代码
}

当一个对象准备被垃圾回收时,垃圾回收器会调用该对象的finalize()方法。默认情况下,finalize()方法是空的,即没有实际的操作。如果需要执行一些清理操作,可以在子类中重写该方法,并在其中编写相应的代码。

需要注意的是,finalize()方法的调用并不是确定性的,也无法保证一定会被调用。垃圾回收器的工作是由JVM自动管理的,它在合适的时机进行垃圾回收操作。因此,无法准确控制finalize()方法的调用时机和频率。

另外,从Java 9开始,finalize()方法已被标记为@Deprecated,并且在将来的版本中可能会被移除。这是因为finalize()方法的使用方式不可靠且容易导致性能问题。推荐使用其他资源管理方式(如使用try-with-resources语句块关闭资源)来替代finalize()方法的使用。

总之,虽然Java提供了finalize()方法用于对象的清理操作,但在实际开发中应尽量避免依赖于finalize()方法来执行关键的清理逻辑,而是应该使用更可靠和确定性的资源管理方式。

7. 记录-Java 14

7.1 记录概念

Java 14引入了一种新的数据类型,称为"记录"(Records)。记录是一种简洁的类声明形式,旨在表示不可变的数据聚合。它提供了一种简单的方式来定义带有固定集合的数据对象,并自动生成一些标准的方法。

记录具有以下特点:

  1. 属性自动声明:在记录中,可以直接声明属性,而无需显式定义字段和对应的getter和setter方法。
  2. 不可变性:记录的属性默认是final的,一旦初始化后就无法修改。
  3. equals()和hashCode()方法:记录自动提供了基于所有属性的equals()和hashCode()方法。
  4. toString()方法:记录还自动生成了一个toString()方法,方便打印记录的字符串表示。
  5. 构造器:记录的构造器自动根据记录的属性生成,用于初始化属性值。

以下是一个示例记录的定义:

record Person(String name, int age) {
    // 可以在记录类中添加其他方法和字段
    public void sayHello() {
        System.out.println("Hello, my name is " + name);
    }
}

通过以上定义,就创建了一个名为Person的记录,它具有两个属性:name和age。在记录中,这两个属性将自动声明为final,并且自动生成了equals()、hashCode()和toString()方法。

使用记录可以简化代码,特别适用于表示简单的数据聚合。记录在许多场景下都很有用,例如表示数据传输对象(DTO)、数据实体、事件等。

需要注意的是,记录是Java 14及更高版本中引入的特性,因此在使用记录时需要确保项目使用了Java 14或更高的版本。

7.2 构造器: 标准、自定义、简洁

Java的记录(Record)类在构造器方面有一些特点和限制。

  • 标准构造器(Standard Constructor):记录类会自动生成一个标准构造器,用于接受所有字段作为参数并初始化记录对象。例如,对于以下记录类:

    record Person(String name, int age, String gender) {}
    

    将会生成以下标准构造器:

    public Person(String name, int age, String gender) {
        // 自动生成的构造器实现
    }
    
  • 自定义构造器(Custom Constructor):除了标准构造器外,可以在记录类中定义自定义构造器。自定义构造器必须显式声明,并可以根据需要接受部分或全部字段作为参数。例如:

    record Person(String name, int age, String gender) {
        public Person(String name) {
            this(name, 0, "Unknown");
        }
    }
    

    在上述示例中,自定义了一个只接受名称参数的构造器,年龄和性别字段使用默认值初始化。

  • 简洁构造器(Compact Constructor):对于简单的记录类,可以使用简洁构造器的语法,省略构造器的显式声明。简洁构造器会自动使用所有字段作为参数,并使用字段名称来初始化记录对象。例如:

    record Person(String name, int age, String gender) {}
    

    将自动生成以下简洁构造器:

    public Person {
        // 自动生成的简洁构造器实现
    }
    

    可以通过提供字段的值来使用简洁构造器创建记录对象:

    Person person = new Person("John", 25, "Male");
    

8. 包

包(Package)用于组织和管理类和其他相关资源。下面是一些与包相关的重要概念和用法:

  1. 包名(Package Name):包名是用来唯一标识一个包的字符串,通常采用逆域名的命名方式,例如:com.example.myapp。包名应该与对应的目录结构一致,例如:包com.example.myapp对应的目录结构应为com/example/myapp。
  2. 类的导入(Importing Classes):在Java中,通过导入类(import)语句可以引入其他包中的类,以便在当前类中使用。例如:import com.example.myapp.MyClass。
  3. 静态导入(Static Import):静态导入用于引入类的静态成员,以便在代码中直接使用静态成员而无需使用类名前缀。静态导入使用import static语法,例如:import static java.lang.Math.*;。
  4. 在包中增加类(Adding Classes to a Package):要将类放入特定的包中,需要在类的源文件中使用package语句指定包名。例如:package com.example.myapp;。
  5. 包访问(Package Access):当类没有指定访问修饰符(public、protected、private)时,它具有包访问权限,只能被同一包中的其他类访问。
  6. 类路径(Classpath):类路径用于指定Java虚拟机在查找类文件时应搜索的目录或JAR文件。类路径可以通过设置CLASSPATH环境变量或使用命令行参数来指定。
  7. 设置类路径(Setting Classpath):可以使用命令行参数来设置类路径,例如:java -classpath path/to/classes MyApp。还可以在IDE中配置类路径,以便在编译和运行时正确地查找类和资源文件。

9. JAR文件

9.1 创建jar文件

在Java中,可以使用jar工具来创建jar文件。jar文件是Java Archive的缩写,它是一种将多个Java类文件和相关资源文件打包成一个文件的格式。

以下是创建jar文件的步骤:

  1. 编译Java源文件:首先,需要将Java源文件编译成字节码文件(.class文件)。可以使用javac命令编译源文件,例如:

    javac MyClass.java
    
  2. 创建清单文件:清单文件(Manifest)是jar文件的一部分,用于描述jar文件的属性和配置信息。创建一个名为Manifest.txt的文本文件,并在文件中指定主类(如果需要),例如:

    Main-Class: com.example.myapp.Main
    
  3. 打包成jar文件:使用jar工具将编译后的字节码文件和其他资源文件打包成jar文件。可以使用以下命令创建jar文件:

    jar cvf MyApp.jar MyClass.class Manifest.txt
    
    • c 表示创建一个新的jar文件。
    • v 表示在控制台输出详细的打包过程。
    • f 后面是要创建的jar文件的名称。
    • MyClass.class 是要包含在jar文件中的字节码文件。
    • Manifest.txt 是清单文件。

    如果有多个字节码文件和资源文件,可以将它们一起指定在命令行中。

  4. 运行jar文件:创建好的jar文件可以直接运行,可以使用以下命令运行jar文件:

    java -jar MyApp.jar
    

以上步骤只是基本的jar文件创建过程,具体的步骤和配置可能因项目和需求而有所不同。在实际开发中,可以根据需要使用更高级的构建工具(如Maven或Gradle)来自动化构建和打包过程,以简化开发流程。

9.2 清单文件

清单文件(Manifest)是用于描述JAR文件的属性和配置信息的特殊文件。清单文件位于JAR文件的META-INF目录下,命名为MANIFEST.MF。

清单文件使用键值对的形式来指定属性和值。以下是一个简单的清单文件示例:

Manifest-Version: 1.0
Created-By: 1.8.0_261 (Oracle Corporation)
Main-Class: com.example.myapp.Main

清单文件中的常用属性包括:

  • Manifest-Version: 指定清单文件的版本号。
  • Created-By: 指定创建清单文件的工具和版本。
  • Main-Class: 指定JAR文件的入口点(主类),用于执行JAR文件。

除了这些常用属性外,清单文件还可以定义自定义的属性和值,用于应用程序的特定配置。

创建清单文件时,需要按照键值对的格式将属性和值写入文件中,每个键值对占据一行。注意,属性名和冒号之间需要有一个空格,而属性值不需要添加引号或其他特殊字符。

在使用jar工具创建JAR文件时,可以通过指定清单文件来包含自定义的属性和配置信息。例如,在创建JAR文件时使用以下命令:

jar cvfm MyApp.jar Manifest.txt *.class

其中,Manifest.txt是包含清单属性的文本文件,*.class是要包含在JAR文件中的字节码文件。

通过在清单文件中指定Main-Class属性,可以在运行JAR文件时直接指定入口点(主类):

java -jar MyApp.jar

清单文件提供了一种方便的方式来指定JAR文件的属性和配置信息,使得JAR文件可以更灵活和可配置。

9.3 多版本jar文件

从Java 9开始,引入了一种名为"多版本JAR文件"(Multi-Release JAR)的机制,用于支持在同一个JAR文件中包含不同Java版本的类文件。

多版本JAR文件允许开发人员为不同的Java版本提供特定的实现。当运行在支持多版本JAR的Java运行时环境中时,可以根据当前运行的Java版本自动选择合适的类文件。

在多版本JAR文件中,每个Java版本的类文件应该放置在与其对应的目录中。例如,对于Java 9的类文件,应该放在META-INF/versions/9目录下,对于Java 10的类文件,应该放在META-INF/versions/10目录下,依此类推。

以下是一个多版本JAR文件的示例目录结构:

myapp.jar
|
+-- com/example/MyClass.class (默认版本,兼容所有Java版本)
|
+-- META-INF/versions/9/com/example/MyClass.class (适用于Java 9的版本)
|
+-- META-INF/versions/10/com/example/MyClass.class (适用于Java 10的版本)

使用多版本JAR文件时,Java运行时环境会根据当前的Java版本自动选择合适的类文件。如果运行在不支持多版本JAR的旧版本Java环境中,则会选择默认版本的类文件。

要创建多版本JAR文件,可以使用Java编译器(javac)的--release选项指定要兼容的最低Java版本,并将不同版本的类文件放置在相应的目录中。然后使用jar工具创建JAR文件,确保目录结构正确。

例如,要创建一个适用于Java 9和Java 10的多版本JAR文件,可以使用以下命令:

javac --release 9 --class-path src -d out src/com/example/MyClass.java
javac --release 10 --class-path src -d out/META-INF/versions/10 src/com/example/MyClass.java
jar cvf myapp.jar -C out .

这将编译com/example/MyClass.java文件,并将默认版本的类文件放置在out目录下,将适用于Java 10的类文件放置在out/META-INF/versions/10目录下。然后使用jar工具将这些文件打包成一个多版本JAR文件。

使用多版本JAR文件可以提供更好的向后兼容性,并允许开发人员在同一个JAR文件中为不同的Java版本提供特定的实现。

9.4 命令行选项

在Java中,命令行选项是在命令行中传递给Java应用程序的参数,用于控制程序的行为和配置。Java提供了一些内置的命令行选项,同时也允许开发人员自定义和解析自己的命令行选项。

以下是一些常见的Java内置命令行选项:

  • -classpath-cp:指定Java类路径,用于指定程序运行时查找类和资源的路径。
  • -version:显示Java版本信息。
  • -Xmx:指定Java虚拟机的最大堆内存大小。
  • -Xms:指定Java虚拟机的初始堆内存大小。
  • -D:设置系统属性,用于传递键值对形式的参数给程序。

除了内置选项,开发人员可以使用第三方库如Apache Commons CLI或picocli等来解析和处理自定义的命令行选项。这些库提供了更高级的命令行解析功能,如支持参数选项、帮助文档生成、自动完成等。

要解析命令行选项,通常需要定义选项的名称、缩写、参数类型和描述。然后,使用命令行解析库来解析并提取命令行中的选项和参数值。

以下是一个使用Apache Commons CLI库解析命令行选项的示例:

import org.apache.commons.cli.*;

public class CommandLineExample {
    public static void main(String[] args) {
        Options options = new Options();
        options.addOption("h", "help", false, "Display help message");
        options.addOption("v", "verbose", false, "Enable verbose output");
        options.addOption("f", "file", true, "Specify input file");

        CommandLineParser parser = new DefaultParser();
        try {
            CommandLine cmd = parser.parse(options, args);

            if (cmd.hasOption("h")) {
                printHelp(options);
            }

            if (cmd.hasOption("v")) {
                System.out.println("Verbose mode enabled");
            }

            if (cmd.hasOption("f")) {
                String inputFile = cmd.getOptionValue("f");
                System.out.println("Input file: " + inputFile);
            }
        } catch (ParseException e) {
            System.err.println("Error parsing command line options: " + e.getMessage());
            printHelp(options);
        }
    }

    private static void printHelp(Options options) {
        HelpFormatter formatter = new HelpFormatter();
        formatter.printHelp("java -jar MyApp.jar", options);
        System.exit(0);
    }
}

以上示例使用了Apache Commons CLI库来定义并解析三个命令行选项:-h-v-f。如果命令行中包含-h选项,则显示帮助信息;如果包含-v选项,则输出"Verbose mode enabled";如果包含-f选项,则输出指定的输入文件。


10. 文档注释

Java文档注释是一种特殊的注释,用于对代码进行详细的解释和文档化。它们以/** ... */的格式包围,通常用于描述类、方法、字段和包等元素的用途、行为和使用方式。

下面是一些常见的Java文档注释的用法:

  1. 类注释:用于描述类的用途、功能和设计目的。

    /**
     * This class represents a car object.
     * It provides methods for starting, stopping, and accelerating the car.
     */
    public class Car {
        // class implementation
    }
    
  2. 方法注释:用于描述方法的功能、参数、返回值和异常。

    /**
     * Calculates the sum of two numbers.
     * @param a the first number
     * @param b the second number
     * @return the sum of the two numbers
     * @throws IllegalArgumentException if the input numbers are invalid
     */
    public int sum(int a, int b) {
        // method implementation
    }
    
  3. 字段注释:用于描述字段的用途和含义。

    /**
     * The name of the person.
     */
    private String name;
    
  4. 通用注释:用于提供其他通用的信息,如作者、版本、日期等。

    /**
     * Utility class for performing string operations.
     * @version 1.0
     * @author John Doe
     * @since 2022-01-01
     */
    public class StringUtils {
        // class implementation
    }
    
  5. 包注释:用于描述整个包的用途和内容。

    /**
     * Provides utility classes for file operations.
     */
    package com.example.fileutils;
    

Java文档注释具有一种特殊的格式,可以使用工具(如Javadoc)从源代码中提取注释并生成HTML格式的文档。这些生成的文档可以用作项目的API文档,帮助其他开发人员理解和使用代码。

11. 类设计技巧

  • 一定要保证数据私有
  • 一定要初始化数据
  • 不要在类中使用过多基本类型
  • 不是所有的字段都需要单独的字段访问器和更改器
  • 分解有过多职责的类
  • 类名和方法名要能够体现它们的职责
  • 优先使用不可变的类

在Java类的设计过程中,有一些技巧可以帮助您编写出更具可读性、可维护性和可扩展性的代码。以下是一些常见的Java类设计技巧:

  1. 单一职责原则(Single Responsibility Principle):每个类应该只有一个单一的责任。将不同的功能分离到不同的类中,使得每个类的职责清晰明确。
  2. 封装(Encapsulation):通过将数据和方法封装在类中,限制对类内部的直接访问,并提供公共的接口方法来操作数据。这样可以隐藏实现细节,增加代码的可维护性和安全性。
  3. 继承(Inheritance):使用继承来实现类之间的层次关系,共享通用的属性和方法。继承可以提高代码的重用性和扩展性,但应遵循"is-a"关系,确保子类是父类的一种特殊类型。
  4. 多态(Polymorphism):利用多态性,同一个方法可以根据不同的对象表现出不同的行为。多态提高了代码的灵活性和可扩展性,使得代码更加通用和可重用。
  5. 接口(Interface):使用接口定义类的行为规范,使得类之间可以通过接口进行松耦合的交互。接口提供了一种约定,让不同的类可以实现相同的接口并提供自己的实现逻辑。
  6. 组合(Composition):通过将多个对象组合成一个更大的对象,以表示更复杂的关系。组合关系可以提供更灵活的对象结构,而不必依赖于继承关系。
  7. 设计模式(Design Patterns):应用常见的设计模式来解决特定的设计问题,如单例模式、工厂模式、观察者模式等。设计模式提供了一种被广泛接受的解决方案,可以提高代码的可读性和可维护性。
  8. 设计原则(Design Principles):遵循一些设计原则,如开闭原则、里氏替换原则、依赖倒置原则等。这些原则提供了一些指导性的准则,帮助您编写出高质量、可扩展和可维护的代码。
  9. 清晰命名(Clear Naming):使用清晰、有意义的命名来命名类、方法和变量,使得代码的意图易于理解。遵循命名规范和约定,提高代码的可读性和可理解性。
  10. 注释和文档化(Comments and Documentation):为类、方法和重要的代码块添加注释,解释其功能和用法。使用Java文档注释来生成API文档,为其他开发人员提供准确和详细的文档。