自在学
分类课程智能体订阅
分类课程AI导师价格
课程进度
8 / 10
上一节Java数组下一节Java继承
自在学

© 2025 - 2026 自在学,保留所有权利。

公网安备湘公网安备43020302000292号 | 湘ICP备2025148919号-1

关于我们隐私政策使用条款

© 2025 自在学,保留所有权利。

公网安备湘公网安备43020302000292号湘ICP备2025148919号-1

编程JavaJava类进阶

Java类进阶

Java类进阶

静态类成员

在之前的部分,我们学习了实例字段和实例方法。每个类的实例都有自己的字段集合,这些字段被称为实例字段。你可以创建类的几个实例,并在每个实例的字段中存储不同的值。

但是,有时候我们需要创建不属于任何类实例的字段或方法。这样的成员被称为静态字段和静态方法。当值存储在静态字段中时,它不是存储在类的实例中。事实上,即使类的实例不存在,也可以将值存储在类的静态字段中。

同样,静态方法不操作属于任何类实例的字段。相反,它们只能操作静态字段。你可以将静态字段和静态方法视为属于类而不是类的实例。

静态字段

当一个字段使用static关键字声明时,这个字段属于整个类本身,而不是某个具体的对象实例。无论你创建了多少个该类的对象,内存中都只会有这一个静态字段的副本。 所有该类的实例都共享同一个静态字段,这意味着如果一个实例修改了静态字段的值,其他所有实例访问这个字段时看到的也是被修改后的新值。静态字段通常用于存储与类相关的全局信息,而不是与某个特定对象相关的数据。

让我们看一个例子:

java
public class Countable {
    private static int instanceCount = 0;
    
    /**
     * 构造函数增加静态字段instanceCount。
     * 这跟踪创建的此类实例的数量。
     */
    public Countable() {
        instanceCount++;
    }
    
    /**
     * getInstanceCount方法返回已创建的此类实例的数量。
     * @return instanceCount字段中的值。
     */
    public int getInstanceCount() {
        return instanceCount;
    }
}

首先,注意第2行静态字段instanceCount的声明:

java
private static int instanceCount = 0;

静态字段是通过在访问说明符之后、字段数据类型之前放置static关键字创建的。注意我们明确地将instanceCount字段初始化为值0。这种初始化只发生一次,无论创建了多少个类实例。

接下来,看第7到10行的构造函数。构造函数使用++操作符来增加instanceCount字段。每次创建Countable类的实例时,都会调用构造函数,instanceCount字段会增加。因此,instanceCount字段将包含已创建的Countable类实例的数量。

静态字段的演示

让我们看一个演示这个类的程序:

java
public class StaticDemo {
    public static void main(String[] args) {
        int objectCount;
        
        // 创建Countable类的三个实例
        Countable object1 = new Countable();
        Countable object2 = new Countable();
        Countable object3 = new Countable();
        
        // 从类的静态字段获取实例数量
        objectCount = object1.getInstanceCount();
        System.out.println(objectCount + "个类实例被创建。");
    }
}

运行这个程序会输出:

console
3个类实例被创建。

程序创建了Countable类的三个实例,由变量object1、object2和object3引用。虽然有三个类实例,但只有一个静态字段副本。这说明了所有类实例共享静态字段。

静态方法

当类包含静态方法时,不需要创建类的实例就可以执行该方法。让我们看一个包含静态方法的类的例子:

java
public class Metric {
    /**
     * milesToKilometers方法将英里距离转换为公里。
     * @param m 英里距离
     * @return 公里距离
     */
    public static double milesToKilometers(double m) {
        return m * 1.609;
    }
    
    /**
     * kilometersToMiles方法将公里距离转换为英里。
     * @param k 公里距离
     * @return 英里距离
     */
    public static double kilometersToMiles(double k) {
        return k / 1.609;
    }
}

静态方法是通过在方法头中的访问说明符之后放置static关键字创建的。Metric类有两个静态方法:milesToKilometers和kilometersToMiles。因为它们被声明为静态的,所以它们属于类,可以在没有任何类实例存在的情况下调用。

你只需在方法调用中的点操作符之前写入类的名称。这里是一个例子:

java
kilometers = Metric.milesToKilometers(10.0);

这个语句调用milesToKilometers方法,传递值10.0作为参数。注意,该方法不是从类的实例调用的,而是直接从Metric类调用的。

静态方法的演示

让我们看一个使用Metric类的程序:

java
import javax.swing.JOptionPane;
 
public class MetricDemo {
    public static void main(String[] args) {
        String input; // 保存输入
        double miles; // 英里距离
        double kilos; // 公里距离
        
        // 获取英里距离
        input = JOptionPane.showInputDialog("输入英里距离。");
        miles = Double.parseDouble(input);
        
        // 将距离转换为公里
        kilos = Metric.milesToKilometers(miles);
        JOptionPane.showMessageDialog(null,
            String.format("%,.2f英里等于%,.2f公里。", miles, kilos));
        
        // 获取公里距离
        input = JOptionPane.showInputDialog("输入公里距离:");
        kilos = Double.parseDouble(input);
        
        // 将距离转换为英里
        miles = Metric.kilometersToMiles(kilos);
        JOptionPane.showMessageDialog(null,
            String.format("%,.2f公里等于%,.2f英里。", kilos, miles));
        
        System.exit(0);
    }
}

运行这个程序会显示对话框,让用户输入距离并进行转换。

静态方法的限制

静态方法有一个重要的限制:它们不能直接访问类的非静态成员变量或调用非静态方法。原因在于,静态方法属于类本身,而非静态成员属于类的具体实例。当没有创建任何对象实例时,静态方法依然可以被调用,此时并不存在任何实例成员可供访问。 具体来说:

  • 在静态方法内部,不能直接访问非静态字段(成员变量),否则编译器会报错。例如,不能在静态方法中写 System.out.println(this.someField);,因为this关键字在静态方法中是不可用的。
  • 静态方法只能调用同样是静态的方法,不能直接调用非静态方法。
  • 如果静态方法需要访问类的字段或方法,这些字段或方法也必须被声明为static。

将对象作为参数传递给方法

我们之前讨论了如何将基本值以及String对象的引用作为参数传递给方法。你也可以将其他类型对象的引用作为参数传递给方法。

当你将对象作为参数传递时,你传递的是引用变量中的值。当方法接收到对象引用作为参数时,它可以修改该变量引用的对象的内容。

让我们看一个例子:

java
public class PassObject {
    public static void main(String[] args) {
        // 创建一个Rectangle对象
        Rectangle box = new Rectangle(12.0, 5.0);
        
        // 将对对象的引用传递给displayRectangle方法
        displayRectangle(box);
    }
    
    /**
     * displayRectangle方法显示矩形的长度和宽度
     * @param r 对Rectangle对象的引用
     */
    public static void displayRectangle(Rectangle r) {
        // 显示长度和宽度
        System.out.println("长度:" + r.getLength() + " 宽度:" + r.getWidth());
    }
}

运行这个程序会输出:

console
长度:12.0 宽度:5.0

在这个程序的main方法中,box变量是一个Rectangle引用变量。在第8行,它的值作为参数传递给displayRectangle方法。displayRectangle方法有一个参数变量r,它也是一个Rectangle引用变量,接收参数。

修改传递的对象

当方法接收到对象引用作为参数时,方法可以修改该变量引用的对象的内容。这在下例中得到了演示:

java
public class PassObject2 {
    public static void main(String[] args) {
        // 创建一个Rectangle对象
        Rectangle box = new Rectangle(12.0, 5.0);
        
        // 显示对象的内容
        System.out.println("box对象的内容:");
        System.out.println("长度:" + box.getLength() + " 宽度:" + box.getWidth());
        
        // 将对对象的引用传递给changeRectangle方法
        changeRectangle(box);
        
        // 再次显示对象的内容
        System.out.println("\n现在box对象的内容是:");
        System.out.println("长度:" + box.getLength() + " 宽度:" + box.getWidth());
    }
    
    /**
     * changeRectangle方法将Rectangle对象的长度和宽度设置为0
     * @param r 要更改的Rectangle对象
     */
    public static void changeRectangle(Rectangle r) {
        r.setLength(0.0);
        r.setWidth(0.0);
    }
}

运行这个程序会输出:

console
box对象的内容:
长度:12.0 宽度:5.0
现在box对象的内容是:
长度:0.0 宽度:0.0

从方法返回对象

在Java中,方法不仅可以返回基本数据类型的值(如int、double、float等),还可以返回对象的引用。也就是说,方法的返回类型可以是一个类名,这样的方法在执行完毕后会返回对某个对象的引用。通过这种方式,方法能够将新创建的对象、已经存在的对象,或者根据某些逻辑处理后得到的对象引用返回给调用者。这使得我们可以在方法之间灵活地传递和操作对象,极大地增强了程序的结构和功能。

返回对象

返回对象的例子

让我们看一个例子,其中方法返回对BankAccount对象的引用:

java
import javax.swing.JOptionPane;
 
public class ReturnObject {
    public static void main(String[] args) {
        BankAccount account;
        
        // 获取对BankAccount对象的引用
        account = getAccount();
        
        // 显示账户余额
        JOptionPane.showMessageDialog(null,
            "账户余额为$" + account.getBalance());
        
        System.exit(0);
    }
    
    /**
     * getAccount方法创建一个BankAccount对象,
     * 余额由用户指定。
     * @return 对对象的引用
     */
    public static BankAccount getAccount() {
        String input; // 保存输入
        double balance; // 账户余额
        
        // 从用户获取余额
        input = JOptionPane.showInputDialog("输入账户余额。");
        balance = Double.parseDouble(input);
        
        // 创建BankAccount对象并返回对它的引用
        return new BankAccount(balance);
    }
}

注意getAccount方法有一个BankAccount的返回数据类型。这表示方法返回对BankAccount对象的引用。

对象传递与返回可视化仪


toString方法

toString方法的概念

我们经常需要显示表示对象状态的消息。对象的状态简单地说就是存储在对象字段中的数据。例如,BankAccount类有一个字段:balance。在任何给定时刻,BankAccount对象的balance字段将保存某个值。balance字段的值表示该时刻对象的状态。

创建表示对象状态的字符串是如此常见的任务,以至于许多程序员为他们的类配备了一个返回这种字符串的方法。在Java中,将此方法命名为toString是标准做法。

toString方法的例子

让我们看一个具有toString方法的类的例子。Stock类保存有关公司股票的数据:

java
public class Stock {
    private String symbol; // 股票交易符号
    private double sharePrice; // 每股当前价格
    
    /**
     * 构造函数
     * @param sym 股票交易符号
     * @param price 股票每股价格
     */
    public Stock(String sym, double price) {
        symbol = sym;
        sharePrice = price;
    }
    
    /**
     * getSymbol方法
     * @return 股票交易符号
     */
    public String getSymbol() {
        return symbol;
    }
    
    /**
     * getSharePrice方法
     * @return 股票每股价格
     */
    public double getSharePrice() {
        return sharePrice;
    }
    
    /**
     * toString方法
     * @return 表示对象交易符号和每股价格的字符串
     */
    public String toString() {
        // 创建描述股票的字符串
        String str = "交易符号:" + symbol +
                    "\n每股价格:" + sharePrice;
        
        // 返回字符串
        return str;
    }
}

自动调用toString方法

当你为类编写了toString方法后,Java会在需要将对象转换为字符串时自动调用该方法。最常见的场景包括:

  1. 当你将对象作为参数传递给System.out.print或System.out.println等输出方法时,Java会自动调用该对象的toString方法,将其返回的字符串输出到控制台。例如:
java
Stock xyzCompany = new Stock("XYZ", 9.62);
System.out.println(xyzCompany);

Java还会在你将类的对象与字符串连接时隐式调用对象的toString方法。例如,以下代码会隐式调用xyzCompany对象的toString方法:

java
Stock xyzCompany = new Stock("XYZ", 9.62);
System.out.println("股票数据是:\n" + xyzCompany);

编写equals方法

equals方法的概念

在Java中,不能仅仅通过使用==操作符来判断两个对象是否拥有相同的数据。==操作符在比较对象时,实际上比较的是它们在内存中的地址(即引用),而不是对象内部存储的数据内容。因此,即使两个对象的属性值完全相同,只要它们是不同的实例,==操作符的结果也会是false。

如果我们希望判断两个对象的内容是否相同,就需要在类中专门编写一个方法来实现内容的比较。Java为此提供了equals方法。通过重写equals方法,我们可以自定义对象内容的比较逻辑,使得可以根据实际需求判断两个对象的数据是否一致。例如,在Stock类中,可以通过重写equals方法来比较两个Stock对象的symbol和sharePrice字段,从而判断它们是否代表相同的股票信息。

为什么需要equals方法

你不能使用==操作符来比较两个对象的内容。例如,以下代码可能看起来比较两个Stock对象的内容,但实际上没有:

java
// 创建具有相同值的两个Stock对象
Stock company1 = new Stock("XYZ", 9.62);
Stock company2 = new Stock("XYZ", 9.62);
 
// 使用==操作符比较对象(这是错误的)
if (company1 == company2) {
    System.out.println("两个对象相同。");
} else {
    System.out.println("对象不同。");
}

当你使用==操作符与引用变量时,操作符比较变量包含的内存地址,而不是变量引用的对象的内容。因为这段代码中的两个数组变量引用内存中的不同对象,它们将包含不同的地址。因此,布尔表达式company1 == company2的结果为false,代码报告对象不同。

编写equals方法

让我们为Stock类编写一个equals方法:

java
public boolean equals(Stock object2) {
    boolean status;
    
    // 确定此对象的symbol和sharePrice字段是否等于object2的symbol和sharePrice字段
    if (symbol.equals(object2.symbol) && sharePrice == object2.sharePrice) {
        status = true; // 是的,对象相等
    } else {
        status = false; // 不,对象不相等
    }
    
    // 返回status中的值
    return status;
}

equals方法接受一个Stock对象作为其参数。参数变量object2将引用作为参数传递的对象。if语句执行以下比较:如果调用对象的symbol字段等于object2的symbol字段,并且调用对象的sharePrice字段等于object2的sharePrice字段,那么两个对象包含相同的值。

使用equals方法的例子

让我们看一个演示equals方法的程序:

java
public class StockCompare {
    public static void main(String[] args) {
        // 创建具有相同值的两个Stock对象
        Stock company1 = new Stock("XYZ", 9.62);
        Stock company2 = new Stock("XYZ", 9.62);
        
        // 使用equals方法比较对象
        if (company1.equals(company2)) {
            System.out.println("两个对象相同。");
        } else {
            System.out.println("对象不同。");
        }
    }
}

运行这个程序会输出:

console
两个对象相同。

复制对象的方法

复制对象的概念

在 Java 中,不能像复制基本数据类型变量那样,直接通过赋值语句来复制一个对象。对于基本类型(如 int、double 等),使用赋值语句会将变量的值完整地复制到另一个变量中。但对于对象,赋值语句只是复制对象的引用(即内存地址),而不会创建一个全新的对象副本。来看下面的代码示例:

java
Stock company1 = new Stock("XYZ", 9.62);
Stock company2 = company1;

第一个语句创建一个Stock对象并将其地址赋给company1变量。第二个语句将company1赋给company2。这不会复制company1引用的对象。相反,它复制存储在company1中的地址并将该地址存储在company2中。执行此语句后,company1和company2变量都将引用同一个对象。

这种类型的赋值操作称为引用复制,因为只复制对象的地址,而不是实际对象本身。要复制对象本身,你必须创建一个新对象,然后将新对象的字段设置为与被复制对象的字段相同的值。

复制方法

让我们为Stock类添加一个copy方法:

java
public Stock copy() {
    // 创建一个新的Stock对象并用调用对象持有的相同数据初始化它
    Stock copyObject = new Stock(symbol, sharePrice);
    
    // 返回对新对象的引用
    return copyObject;
}

copy方法创建一个新的Stock对象,并将调用对象的symbol和sharePrice字段作为参数传递给构造函数。这使新对象成为调用对象的副本。

复制构造函数的例子

另一种创建对象副本的方法是使用复制构造函数。复制构造函数是接受同一类对象作为参数的构造函数。它使正在创建的对象成为作为参数传递的对象的副本。

让我们为Stock类添加一个复制构造函数:

java
public Stock(Stock object2) {
    symbol = object2.symbol;
    sharePrice = object2.sharePrice;
}

注意构造函数接受一个Stock对象作为参数。参数变量object2将引用作为参数传递的对象。构造函数将object2的symbol和sharePrice字段中的值复制到正在创建的对象的symbol和sharePrice字段中。


聚合

聚合的概念

聚合是指一个类的实例是另一个类中的字段。在现实生活中,对象经常由其他对象组成。例如,房子由门对象、窗对象、墙对象等组成。正是所有这些对象的组合构成了房子对象。

在设计软件时,有时从其他对象创建对象是有意义的。例如,假设你需要一个对象来表示你在大学里正在学习的课程。你决定创建一个Course类,它将保存以下信息:

  • 课程名称
  • 教师的姓氏、名字和办公室号码
  • 教科书的标题、作者和出版商

除了课程名称外,该类还将保存与教师和教科书相关的项目。你可以将每个项目的字段放在Course类中。但是,一个好的设计原则是将相关项目分离到它们自己的类中。

聚合的例子

让我们看看如何做到这一点。Instructor类可以创建来保存教师相关的数据,TextBook类可以创建来保存教科书相关的数据。这些类的实例然后可以用作Course类中的字段。

首先,Instructor类的定义如下:

java
public class Instructor {
    private String lastName; // 姓氏
    private String firstName; // 名字
    private String officeNumber; // 办公室号码
    
    /**
     * 此构造函数初始化姓氏、名字和办公室号码
     * @param lname 教师的姓氏
     * @param fname 教师的名字
     * @param office 办公室号码
     */
    public Instructor(String lname, String fname, String office) {
        lastName = lname;
        firstName = fname;
        officeNumber = office;
    }
    
    /**
     * 复制构造函数将对象初始化为另一个Instructor对象的副本
     * @param object2 要复制的对象
     */
    public Instructor(Instructor object2) {
        lastName = object2.lastName;
        firstName = object2.firstName;
        officeNumber = object2.officeNumber;
    }
    
    /**
     * set方法为每个字段设置值
     * @param lname 教师的姓氏
     * @param fname 教师的名字
     * @param office 办公室号码
     */
    public void set(String lname, String fname, String office) {
        lastName = lname;
        firstName = fname;
        officeNumber = office;
    }
    
    /**
     * toString方法
     * @return 包含教师信息的字符串
     */
    public String toString() {
        // 创建表示对象的字符串
        String str = "姓氏:" + lastName +
                    "\n名字:" + firstName +
                    "\n办公室号码:" + officeNumber;
        
        // 返回字符串
        return str;
    }
}

接下来,我们定义TextBook类,定义如下:

java
public class TextBook {
    private String title; // 书籍标题
    private String author; // 作者姓氏
    private String publisher; // 出版商名称
    
    /**
     * 此构造函数初始化标题、作者和出版商字段
     * @param textTitle 书籍标题
     * @param auth 作者姓名
     * @param pub 出版商名称
     */
    public TextBook(String textTitle, String auth, String pub) {
        title = textTitle;
        author = auth;
        publisher = pub;
    }
    
    /**
     * 复制构造函数将对象初始化为另一个TextBook对象的副本
     * @param object2 要复制的对象
     */
    public TextBook(TextBook object2) {
        title = object2.title;
        author = object2.author;
        publisher = object2.publisher;
    }
    
    /**
     * set方法为每个字段设置值
     * @param textTitle 书籍标题
     * @param auth 作者姓名
     * @param pub 出版商名称
     */
    public void set(String textTitle, String auth, String pub) {
        title = textTitle;
        author = auth;
        publisher = pub;
    }
    
    /**
     * toString方法
     * @return 包含教科书信息的字符串
     */
    public String toString() {
        // 创建表示对象的字符串
        String str = "标题:" + title +
                    "\n作者:" + author +
                    "\n出版商:" + publisher;
        
        // 返回字符串
        return str;
    }
}

最后,我们定义Course类,定义如下:

java
public class Course {
    private String courseName; // 课程名称
    private Instructor instructor; // 教师
    private TextBook textBook; // 教科书
    
    /**
     * 此构造函数初始化courseName、instructor和text字段
     * @param name 课程名称
     * @param instructor Instructor对象
     * @param text TextBook对象
     */
    public Course(String name, Instructor instr, TextBook text) {
        // 分配courseName
        courseName = name;
        
        // 创建一个新的Instructor对象,将instr作为参数传递给复制构造函数
        instructor = new Instructor(instr);
        
        // 创建一个新的TextBook对象,将text作为参数传递给复制构造函数
        textBook = new TextBook(text);
    }
    
    /**
     * getName方法
     * @return 课程名称
     */
    public String getName() {
        return courseName;
    }
    
    /**
     * getInstructor方法
     * @return 对此课程Instructor对象副本的引用
     */
    public Instructor getInstructor() {
        // 返回instructor对象的副本
        return new Instructor(instructor);
    }
    
    /**
     * getTextBook方法
     * @return 对此课程TextBook对象副本的引用
     */
    public TextBook getTextBook() {
        // 返回textBook对象的副本
        return new TextBook(textBook);
    }
    
    /**
     * toString方法
     * @return 包含课程信息的字符串
     */
    public String toString() {
        // 创建表示对象的字符串
        String str = "课程名称:" + courseName +
                    "\n教师信息:\n" + instructor +
                    "\n教科书信息:\n" + textBook;
        
        // 返回字符串
        return str;
    }
}

聚合的演示

让我们看一个演示Course类的程序:

java
public class CourseDemo {
    public static void main(String[] args) {
        // 创建一个Instructor对象
        Instructor myInstructor = new Instructor("张", "三", "RH3010");
        
        // 创建一个TextBook对象
        TextBook myTextBook = new TextBook("Java入门", "李明", "教育出版社");
        
        // 创建一个Course对象
        Course myCourse = new Course("Java编程入门", myInstructor, myTextBook);
        
        // 显示课程信息
        System.out.println(myCourse);
    }
}

运行这个程序会输出:

console
课程名称:Java编程入门
教师信息:
姓氏:张
名字:三
办公室号码:RH3010
教科书信息:
标题:Java入门
作者:李明
出版商:教育出版社

this引用变量

this关键字的概念

关键字this是对象可以用来引用自己的引用变量的名称。例如,回忆我们前面介绍的Stock类。该类有一个equals方法,它将调用的Stock对象与作为参数传递的另一个Stock对象进行比较:

java
public boolean equals(Stock object2) {
    boolean status;
    
    // 确定此对象的symbol和sharePrice字段是否等于object2的symbol和sharePrice字段
    if (symbol.equals(object2.symbol) && sharePrice == object2.sharePrice) {
        status = true; // 是的,对象相等
    } else {
        status = false; // 不,对象不相等
    }
    
    // 返回status中的值
    return status;
}

当此方法执行时,this变量包含调用对象的地址。我们可以重写if语句如下,它将执行相同的操作:

java
if (this.symbol.equals(object2.symbol) && this.sharePrice == object2.sharePrice)

使用this克服阴影

this关键字的一个常见用途是克服参数名称对字段名称的阴影。如果方法的参数与同一类中的字段具有相同的名称,则参数名称会遮蔽字段名称。

例如,看Stock类中的构造函数:

java
public Stock(String sym, double price) {
    symbol = sym;
    sharePrice = price;
}

这个方法使用参数sym来接受分配给symbol字段的参数,使用参数price来接受分配给sharePrice字段的参数。有时很难(甚至耗时)想出一个与字段名称不同的好参数名称。为了避免这个问题,许多程序员给参数与它们对应的字段相同的名称,然后使用this关键字来引用字段名称。

例如,Stock类的构造函数可以写成如下:

java
public Stock(String symbol, double sharePrice) {
    this.symbol = symbol;
    this.sharePrice = sharePrice;
}

虽然参数名称symbol和sharePrice遮蔽了字段名称symbol和sharePrice,但this关键字克服了遮蔽。因为this是对调用对象的引用,表达式this.symbol引用调用对象的symbol字段,表达式this.sharePrice引用调用对象的sharePrice字段。

使用this调用重载构造函数

你已经知道当创建对象时会自动调用构造函数。你也知道不能像调用其他方法那样显式调用构造函数。但是,有一个例外:你可以使用this关键字从同一类中的另一个构造函数调用一个构造函数。

为了说明这一点,请看下面的例子,它有以下构造函数:

java
public Stock(String sym, double price) {
    symbol = sym;
    sharePrice = price;
}

这个构造函数接受分配给symbol和sharePrice字段的参数。假设我们还想要一个只接受symbol字段参数的构造函数,并将0.0分配给sharePrice字段。以下是编写构造函数的一种方法:

java
public Stock(String sym) {
    this(sym, 0.0);
}

这个构造函数简单地使用this变量调用第一个构造函数。它将sym中的值作为第一个参数传递,将0.0作为第二个参数传递。结果是symbol字段被分配sym中的值,sharePrice字段被分配0.0。

记住关于使用this调用构造函数的以下规则:

  • this只能用于从同一类中的另一个构造函数调用构造函数
  • 它必须是进行调用的构造函数中的第一个语句。如果不是第一个语句,将导致编译器错误

枚举类型

枚举类型的概念

枚举数据类型由一组预定义值组成。你可以使用数据类型创建只能保存属于枚举数据类型的值的变量。

你已经学习了数据类型的概念以及它们如何与基本变量一起使用。例如,int数据类型的变量可以在特定范围内保存整数值。你不能将浮点值分配给int变量,因为只有int值可以分配给int变量。数据类型定义了该数据类型任何变量的合法值。

有时创建具有特定合法值集的自己的数据类型是有帮助的。例如,假设你想创建一个名为Day的数据类型,该数据类型中的合法值是星期几的名称(星期日、星期一等)。 当你创建Day数据类型的变量时,你只能在该变量中存储星期几的名称。任何其他值都是非法的。在Java中,这种类型被称为枚举数据类型。

创建枚举类型

你使用enum关键字创建自己的数据类型并指定属于该类型的值。以下是枚举数据类型声明的例子:

java
enum Day { SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY }

枚举数据类型声明以enum关键字开始,后跟类型名称,后跟大括号内的标识符列表。示例声明创建一个名为Day的枚举数据类型。 大括号内列出的标识符SUNDAY、MONDAY、TUESDAY、WEDNESDAY、THURSDAY、FRIDAY和SATURDAY被称为枚举常量。它们表示属于Day数据类型的值。

使用枚举类型

一旦你在程序中创建了枚举数据类型,你就可以声明该类型的变量。例如,以下语句将workDay声明为Day类型的变量:

java
Day workDay;

因为workDay是Day变量,我们可以合法地分配给它的唯一值是枚举常量Day.SUNDAY、Day.MONDAY、Day.TUESDAY、Day.WEDNESDAY、Day.THURSDAY、Day.FRIDAY和Day.SATURDAY。如果我们尝试分配Day类型的枚举常量以外的任何值,将导致编译器错误。

枚举类型是专门的类

当你编写枚举类型声明时,你实际上是在创建一种特殊的类。此外,你在大括号内列出的枚举常量实际上是类的对象。 在前面的例子中,Day是一个类,枚举常量Day.SUNDAY、Day.MONDAY、Day.TUESDAY、Day.WEDNESDAY、Day.THURSDAY、Day.FRIDAY和Day.SATURDAY都是Day类的实例。

枚举类型的方法

枚举常量实际上是对象,它们自动配备了几个方法。其中之一是toString方法。toString方法简单地返回调用枚举常量的名称作为字符串。

枚举常量还有一个名为ordinal的方法。ordinal方法返回表示常量序数值的整数值。常量的序数值是它在枚举声明中的位置,第一个常量位于位置0。

枚举类型的例子

让我们看一个完整的例子:

java
public class EnumDemo {
    // 声明Day枚举类型
    enum Day { SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY }
    
    public static void main(String[] args) {
        // 声明一个Day变量并为其赋值
        Day workDay = Day.WEDNESDAY;
        
        // 以下语句显示WEDNESDAY
        System.out.println(workDay);
        
        // 以下语句显示Day.SUNDAY的序数值,即0
        System.out.println("Day.SUNDAY的序数值是" + Day.SUNDAY.ordinal());
        
        // 以下语句显示Day.SATURDAY的序数值,即6
        System.out.println("Day.SATURDAY的序数值是" + Day.SATURDAY.ordinal());
        
        // 以下语句比较两个枚举常量
        if (Day.FRIDAY.compareTo(Day.MONDAY) > 0) {
            System.out.println(Day.FRIDAY + "大于" + Day.MONDAY);
        } else {
            System.out.println(Day.FRIDAY + "不大于" + Day.MONDAY);
        }
    }
}

运行这个程序会输出:

console
WEDNESDAY
Day.SUNDAY的序数值是0
Day.SATURDAY的序数值是6
FRIDAY大于MONDAY

在switch语句中使用枚举类型

在Java中,switch语句不仅可以用于基本数据类型(如int、char等),还可以直接用于枚举类型。这意味着你可以在switch语句中将枚举常量作为条件进行判断,从而根据不同的枚举值执行不同的代码分支。下面是一个具体的示例,演示了如何在switch语句中使用枚举类型:

java
public class SportsCarDemo2 {
    public static void main(String[] args) {
        // 创建一个SportsCar对象
        SportsCar yourNewCar = new SportsCar(CarType.PORSCHE, CarColor.RED, 100000);
        
        // 获取汽车品牌并对其进行switch
        switch (yourNewCar.getMake()) {
            case PORSCHE:
                System.out.println("你的汽车是在德国制造的。");
                break;
            case FERRARI:
                System.out.println("你的汽车是在意大利制造的。");
                break;
            case JAGUAR:
                System.out.println("你的汽车是在英国制造的。");
                break;
            default:
                System.out.println("我不确定那辆汽车是在哪里制造的。");
        }
    }
}

运行这个程序会输出:

console
你的汽车是在德国制造的。

垃圾回收

垃圾回收的概念

在Java中,内存管理的一项重要机制是垃圾回收(Garbage Collection,简称GC)。Java虚拟机(JVM)会定期启动一个称为垃圾收集器(Garbage Collector)的后台进程,自动检测并清理程序中不再被任何变量引用的对象。这些“无主”的对象占用的内存会被回收,从而为新的对象分配空间,避免内存泄漏。

具体来说,当你在程序中通过new关键字创建对象时,这些对象会被分配在堆内存中。只要有变量(引用)指向这些对象,它们就会一直存在于内存中。当某个对象不再被任何变量引用(即没有任何方式可以再访问到它),这个对象就变成了“不可达对象”。JVM的垃圾收集器会定期扫描内存,发现这些不可达对象后,将其占用的内存空间释放出来。

与C++等需要手动释放内存的语言不同,Java程序员无需显式地销毁对象或释放内存。垃圾回收器会自动完成这些工作。这大大降低了内存泄漏和悬挂指针等问题的风险,提高了程序的健壮性和安全性。

需要注意的是,垃圾回收的具体时机和频率由JVM自行决定,程序员无法精确控制。虽然可以通过调用System.gc()方法建议JVM进行垃圾回收,但这只是一个建议,JVM是否立即执行垃圾回收并不保证。因此,良好的编程习惯是让对象在不再需要时尽早失去引用(如将引用变量赋值为null),其余的内存管理工作交给JVM自动处理。

垃圾回收的例子

例如,看以下代码:

java
// 声明两个BankAccount引用变量
BankAccount account1, account2;
 
// 创建一个对象并用account1引用它
account1 = new BankAccount(500.0);
 
// 用account2引用同一个对象
account2 = account1;
 
// 在account1中存储null,使其不再引用对象
account1 = null;
 
// 对象仍然被account2引用
// 在account2中存储null,使其不再引用对象
account2 = null;
 
// 现在对象不再被引用,所以可以被垃圾收集器删除

这段代码使用两个引用变量account1和account2。创建一个BankAccount对象并由account1引用。然后,account1被赋给account2,这导致account2引用与account1相同的对象。

接下来,null值被赋给account1。这从account1变量中移除对象的地址,使其不再引用对象。对象仍然可以访问,因为它被account2变量引用。下一个语句将null赋给account2。这从account2中移除对象的地址,使其不再引用对象。因为对象不再可访问,它将在下次垃圾收集器进程运行时从内存中删除。

finalize方法

如果类有一个名为finalize的方法,它会在类的实例被垃圾收集器销毁之前自动调用。如果你希望在对象被销毁之前执行代码,可以在类中创建finalize方法并将代码放在那里。finalize方法不接受参数,具有void返回类型。

  • 垃圾收集器定期运行,你无法预测它何时执行。
  • 你无法知道对象的finalize方法何时执行。

习题

  1. 在Java中,用于声明静态成员的关键字是?
  1. 如何访问静态成员?
  1. 在Java中,用于引用当前对象的关键字是?
  1. 在Java中,用于返回对象字符串表示的标准方法是?
  1. 在Java中,比较两个对象的内容是否相等应该使用哪个方法?
  1. 静态字段和实例字段的主要区别是?
  1. 在自定义类中,是否必须重写toString()方法?

8. 静态成员练习

创建一个类,使用静态字段来跟踪创建的实例数量,并提供一个静态方法来获取实例数量。

java
public class Student {
    private String name;
    private static int count = 0;  // 静态字段,跟踪实例数量
    
    // 构造函数
    public Student(String name) {
        this.name = name;
        count++;  // 每次创建对象时,计数加1
    }
    
    // 静态方法,获取实例数量
    public static int getCount() {
        return count;
    }
    
    public String getName() {
        return name;
    }
}
 
// 测试类
public class StaticMemberTest {
    public static void main(String[] args) {
        System.out.println("初始实例数量: " + Student.getCount());
        
        Student student1 = new Student("张三");
        System.out.println("创建1个对象后: " + Student.getCount());
        
        Student student2 = new Student("李四");
        System.out.println("创建2个对象后: " + Student.getCount());
        
        Student student3 = new Student("王五");
        System.out.println("创建3个对象后: " + Student.getCount());
    }
}

输出结果:

console
初始实例数量: 0
创建1个对象后: 1
创建2个对象后: 2
创建3个对象后: 3

说明:

  • count是静态字段,属于类本身,所有实例共享
  • 每次创建对象时,构造函数中count++
  • getCount()是静态方法,可以通过类名直接调用
  • 不需要创建对象就可以访问静态成员

9. toString方法练习

为Student类编写一个toString方法,显示学生的姓名和年龄。

java
public class Student {
    private String name;
    private int age;
    
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    // 重写toString方法
    @Override
    public String toString() {
        return "Student{姓名='" + name + "', 年龄=" + age + "}";
    }
    
    public String getName() {
        return name;
    }
    
    public int getAge() {
        return age;
    }
}
 
// 测试类
public class ToStringTest {
    public static void main(String[] args) {
        Student student = new Student("张三", 20);
        
        // 直接打印对象,会自动调用toString()方法
        System.out.println(student);
        
        // 也可以显式调用toString()方法
        System.out.println(student.toString());
    }
}

输出结果:

console
Student{姓名='张三', 年龄=20}
Student{姓名='张三', 年龄=20}

说明:

  • 使用@Override注解表示重写父类方法
  • toString()方法返回对象的字符串表示
  • 直接打印对象时,会自动调用toString()方法
  • 重写toString()可以让对象以更友好的方式显示

10. this关键字练习

在类的构造函数中使用this关键字来避免参数名称遮蔽字段名称。

java
public class Rectangle {
    private double width;
    private double height;
    
    // 使用this关键字区分参数和字段
    public Rectangle(double width, double height) {
        this.width = width;   // this.width是字段,width是参数
        this.height = height;  // this.height是字段,height是参数
    }
    
    // 也可以使用不同的参数名,避免使用this
    public void setWidth(double w) {
        width = w;  // 参数名不同,不需要this
    }
    
    public void setHeight(double h) {
        height = h;  // 参数名不同,不需要this
    }
    
    public double getWidth() {
        return width;
    }
    
    public double getHeight() {
        return height;
    }
    
    public double getArea() {
        return width * height;
    }
}
 
// 测试类
public class ThisKeywordTest {
    public static void main(String[] args) {
        Rectangle rect = new Rectangle(10.0, 5.0);
        
        System.out.println("宽度: " + rect.getWidth());
        System.out.println("高度: " + rect.getHeight());
        System.out.println("面积: " + rect.getArea());
    }
}

输出结果:

console
宽度: 10.0
高度: 5.0
面积: 50.0

说明:

  • this关键字引用当前对象
  • 当参数名和字段名相同时,使用this.字段名访问字段
  • 使用this可以明确区分参数和字段
  • 如果参数名和字段名不同,可以不使用this

11. equals方法练习

为Student类编写一个equals方法,比较学生的姓名和学号是否相等。

java
public class Student {
    private String name;
    private String studentId;
    
    public Student(String name, String studentId) {
        this.name = name;
        this.studentId = studentId;
    }
    
    // 重写equals方法
    @Override
    public boolean equals(Object obj) {
        // 如果是同一个对象,返回true
        if (this == obj) {
            return true;
        }
        
        // 如果对象为null或类型不同,返回false
        if (obj == null || getClass() != obj.getClass()) {
            return false;
        }
        
        // 转换为Student类型
        Student student = (Student) obj;
        
        // 比较姓名和学号
        return name.equals(student.name) && studentId.equals(student.studentId);
    }
    
    public String getName() {
        return name;
    }
    
    public String getStudentId() {
        return studentId;
    }
}
 
// 测试类
public class EqualsTest {
    public static void main(String[] args) {
        Student student1 = new Student("张三", "2023001");
        Student student2 = new Student("张三", "2023001");
        Student student3 = new Student("李四", "2023002");
        
        // 使用equals方法比较
        System.out.println("student1.equals(student2): " + student1.equals(student2));
        System.out.println("student1.equals(student3): " + student1.equals(student3));
        
        // 注意:==比较的是引用,不是内容
        System.out.println("student1 == student2: " + (student1 == student2));
    }
}

输出结果:

console
student1.equals(student2): true
student1.equals(student3): false
student1 == student2: false

说明:

  • 重写equals()方法用于比较对象内容
  • 先检查是否为同一个对象(==)
  • 检查对象是否为null或类型不同
  • 比较关键字段(姓名和学号)
  • equals()比较内容,==比较引用
  • 静态类成员
    • 静态字段
    • 静态字段的演示
    • 静态方法
    • 静态方法的演示
    • 静态方法的限制
  • 将对象作为参数传递给方法
    • 修改传递的对象
  • 从方法返回对象
    • 返回对象的例子
    • 对象传递与返回可视化仪
  • toString方法
    • toString方法的概念
    • toString方法的例子
    • 自动调用toString方法
  • 编写equals方法
    • equals方法的概念
    • 为什么需要equals方法
    • 编写equals方法
    • 使用equals方法的例子
  • 复制对象的方法
    • 复制对象的概念
    • 复制方法
    • 复制构造函数的例子
  • 聚合
    • 聚合的概念
    • 聚合的例子
    • 聚合的演示
  • this引用变量
    • this关键字的概念
    • 使用this克服阴影
    • 使用this调用重载构造函数
  • 枚举类型
    • 枚举类型的概念
    • 创建枚举类型
    • 使用枚举类型
    • 枚举类型是专门的类
    • 枚举类型的方法
    • 枚举类型的例子
    • 在switch语句中使用枚举类型
  • 垃圾回收
    • 垃圾回收的概念
    • 垃圾回收的例子
    • finalize方法
  • 习题

目录

  • 静态类成员
    • 静态字段
    • 静态字段的演示
    • 静态方法
    • 静态方法的演示
    • 静态方法的限制
  • 将对象作为参数传递给方法
    • 修改传递的对象
  • 从方法返回对象
    • 返回对象的例子
    • 对象传递与返回可视化仪
  • toString方法
    • toString方法的概念
    • toString方法的例子
    • 自动调用toString方法
  • 编写equals方法
    • equals方法的概念
    • 为什么需要equals方法
    • 编写equals方法
    • 使用equals方法的例子
  • 复制对象的方法
    • 复制对象的概念
    • 复制方法
    • 复制构造函数的例子
  • 聚合
    • 聚合的概念
    • 聚合的例子
    • 聚合的演示
  • this引用变量
    • this关键字的概念
    • 使用this克服阴影
    • 使用this调用重载构造函数
  • 枚举类型
    • 枚举类型的概念
    • 创建枚举类型
    • 使用枚举类型
    • 枚举类型是专门的类
    • 枚举类型的方法
    • 枚举类型的例子
    • 在switch语句中使用枚举类型
  • 垃圾回收
    • 垃圾回收的概念
    • 垃圾回收的例子
    • finalize方法
  • 习题